9

This is what I'm doing right now.

var foo = function() {
  var x = someComplicatedComputationThatMayTakeMoreTime();
  this.foo = function() { return x; };
  return x;
}

It works but only if foo is called as a function like so

foo();

But what if I want to call it as a normal variable with a value? I could modify the code to be

var foo = function() {
  var x = someComplicatedComputationThatMayTakeMoreTime();
  this.foo = x;
  return x;
}

That would allow me to only call it once as a function and after that as a regular variable. But it's still not what I want. Plus it gets complicated if it accidentally gets called as a function again, returning an error.

Is this even possible in JavaScript?

BTW, this is for a Chrome/Firefox extension, so IE compatibility does not matter.

Ended up using toString because getters don't allow me to redefine the whole attribute, a function must be associated with it. And toString has cleaner syntax.

Daniel Vassallo
  • 337,827
  • 72
  • 505
  • 443
fent
  • 17,861
  • 15
  • 87
  • 91
  • There's two problems with your use of `this.foo`: 1.) `this` refers to the top-level object (`window`) if called as `foo()`, and 2.) If you assign `foo` to a variable and call that variable many times, `foo` will be updated on every call because that variable isn't getting changed. – Joey Adams Apr 24 '10 at 05:36
  • This was only an example, most of these are defined inside objects so this refers to the object. And I checked with a simple alert() function to see if it was getting called many times or just once and it's the value is only computed once. – fent Apr 24 '10 at 05:46

9 Answers9

7

How about using toString?

var foo = function() {
  function someComplicatedComputationThatMayTakeMoreTime() {
        //your calculations
  }
  return {
      toString: function() { 
           return someComplicatedComputationThatMayTakeMoreTime(); 
      }
  }
}

More about Object-to-Primitive Conversions in JavaScript

EDIT based on comment. Use a singleton (I think it's called):

myObject.prop = (function(){ 
                  function someComplicatedComputationThatMayTakeMoreTime() {
                   //your calculations
                  }
                  return { 
                    toString: function() { 
                     return someComplicatedComputationThatMayTakeMoreTime(); 
                    } 
                  } 
                })()
KooiInc
  • 119,216
  • 31
  • 141
  • 177
  • Wow this works because toString is only called if the variable is used. Thanks. – fent Apr 24 '10 at 18:50
  • 1
    If `foo` is accessed twice, then wouldn't the complicated calculation be done twice? – Casey Chu Apr 24 '10 at 23:08
  • 1
    It would. If you don't want that, you should add an extra property (say: result) in the returned Object. Now add a check to toString: if the result property has no value, do the calculation (in toString), assign its result to the property and return it. If it has a value, return that value. – KooiInc Apr 24 '10 at 23:16
  • This is what I ended up doing and it works. :D var foo = { toString: function() { return foo = someComplicatedComputationThatMayTakeMoreTime(); ; } } The only problem with this right now is if I want to do this with an object's property, I can't access other properties without knowing the name of the object, I was previously using "this.otherproperty". Not that big of a deal, but I'm always looking to make things flexible so I'm using __defineGetter__() for those. – fent Apr 25 '10 at 00:17
  • @LLer: see the edited answer, but maybe you mean something else? – KooiInc Apr 25 '10 at 20:33
  • IMO, this is a very hairy way of going about things. This will only work in cases where the object is implicitly convereted into a string. – Thomas Eding Apr 25 '10 at 21:10
4

If only Internet Explorer didn't exist, you could use getters and setters as described by John Resig in this blog article:

... They allow you to bind special functions to an object that look like normal object properties, but actually execute hidden functions instead.

Daniel Vassallo
  • 337,827
  • 72
  • 505
  • 443
  • Apparently I discover that they are partially supported in IE8, but using different syntax: http://robertnyman.com/2009/05/28/getters-and-setters-with-javascript-code-samples-and-demos/. IE8 is using the `Object.defineProperty` method, which should become the ECMAScript standard. – Daniel Vassallo Apr 24 '10 at 05:27
3

Using a function is your best option for now, however the new JavaScript standard (ECMAScript 5th Ed.) which is being implemented now by all major browser vendors, gives you a method to create accessor properties, where you can define a property with a get and set functions that will be internally called, without worrying to treat this properties as functions, e.g.:

var obj = {};
Object.defineProperty(obj, 'foo', {
  get: function () { // getter logic
    return 'foo!';
  },
  set: function (value) {
    // setter logic
  }
});

obj.foo; // "foo!", no function call

This new standard will take some time to be implemented for all browsers, (the IE9 preview version really disappointed me), and I wouldn't recommend you to use it for production, unless you have total control on the environment where your application will be used.

Christian C. Salvadó
  • 807,428
  • 183
  • 922
  • 838
  • Yes, it's the ES5 standard syntax, from the [TC39 committee](http://www.ecma-international.org/memento/TC39.htm) – Christian C. Salvadó Apr 24 '10 at 05:33
  • I know, I just think the syntax is absolutely horrible. – ChaosPandion Apr 24 '10 at 05:38
  • Yes it's kinda ugly, but this syntax was selected very carefully, as you can see no new grammar has been defined, the method receives a plain old object, introducing new grammar would have made the adoption of the new standard slower, giving more compatibility issues... – Christian C. Salvadó Apr 24 '10 at 06:07
2

What I think you want is a lazily instantiated variable, which can be implemented like this.

var myProperty = null;
function getMyProperty() {
    return (myProperty = myProperty ||  builder());
}
ChaosPandion
  • 77,506
  • 18
  • 119
  • 157
2

This is not practical on the web because IE does not support it, but you can look at https://developer.mozilla.org/en/defineGetter for examples how to do this.

There are a couple ways to do it, here is one example:

var data = {};
data.__defineGetter__("prop",
                      (function () {
                           var value = null;
                           return function () {
                             if (null == value) {
                               value = getYourValueHere();
                             }
                             return value;
                           };
                        })());

and now you can use it like:

var a = data.prop;
var b = data.prop;
Joey Mazzarelli
  • 451
  • 4
  • 5
  • Fortunately this is for a Chrome/Firefox extension, so IE does not matter. :) – fent Apr 24 '10 at 05:26
  • In that case it will work perfectly, I have used it in a Firefox extension too. Check out the docs, there is a friendlier syntax than the one I showed. – Joey Mazzarelli Apr 24 '10 at 05:28
1

I would recommend a variation on ChaosPandion's answer, but with a closure.

var myProperty = (function () {
  var innerProperty = null;
  return function() {
    return (innerProperty = innerProperty ||  someComplicatedComputationThatMayTakeMoreTime());
  };
})();

and then use myProperty() every time you need to access the variable.

Community
  • 1
  • 1
cobbal
  • 69,903
  • 20
  • 143
  • 156
0

You could define a JavaScript getter. From the Apple JavaScript Coding Guidelines:

myObject.__defineGetter__( "myGetter", function() { return this.myVariable; } );
var someVariable = myObject.myGetter;

See John Resig's post, JavaScript Getters and Setters, and the Defining Getters and Setters page at the Mozilla Developer Centre for more information.

Steve Harrison
  • 121,227
  • 16
  • 87
  • 72
0

I would use explicit lazy evaluation. Here's my implementation of it based on Scheme's take:

var delay, lazy, force, promise, promiseForced, promiseRunning;

(function () {

  var getValue = function () {
    return this.value;
  };

  var RUNNING = {};

  var DelayThunk = function (nullaryFunc) {
    this.value = nullaryFunc;
  };
  DelayThunk.prototype.toString = function () {
    return "[object Promise]";
  };
  DelayThunk.prototype.force = function () {
    if (promiseRunning (this)) {
      throw new Error ("Circular forcing of a promise.");
    }
    var nullaryFunc = this.value;
    this.value = RUNNING;
    this.value = nullaryFunc ();
    this.force = getValue;
    return this.value;
  };

  var LazyThunk = function (nullaryFunc) {
    DelayThunk.call (this, nullaryFunc);
  };
  LazyThunk.prototype = new DelayThunk (null);
  LazyThunk.prototype.constructor = LazyThunk;
  LazyThunk.prototype.force = function () {
    var result = DelayThunk.prototype.force.call (this);
    while (result instanceof LazyThunk) {
      result = DelayThunk.prototype.force.call (result);
    }
    return force (result);
  };

  delay = function (nullaryFunc) {
    return new DelayThunk (nullaryFunc);
  };

  lazy = function (nullaryFunc) {
    return new LazyThunk (nullaryFunc);
  };

  force = function (expr) {
    if (promise (expr)) {
      return expr.force ();
    }
    return expr;
  };

  promise = function (expr) {
    return expr instanceof DelayThunk;
  };

  promiseForced = function (expr) {
    return expr.force === getValue || !promise (expr);
  };

  promiseRunning = function (expr) {
    return expr.value === RUNNING || !promise (expr);
  };

}) ();

Example Syntax:

var x = lazy (function () { return expression; });
var y = force (x);

var z = delay (function () { return expression; });
var w = force (z);

Note values are stored once evaluated, so repeated forcing will not do extra computations.

Example usage:

function makeThunk (x, y, z) {
  return lazy (function () {
    // lots of work done here
  });
}

var thunk = makeThunk (arg1, arg2, arg3);

if (condition) {
  output (force (thunk));
  output (force (thunk)); // no extra work done; no extra side effects either
}
Thomas Eding
  • 35,312
  • 13
  • 75
  • 106
  • The usage looks promising. Thanks. This is interesting, I'll try to understand it in a bit. – fent Apr 26 '10 at 06:25
0

You can use the javascript Proxy class for creating such functionality.

var object = {};
var handler = {

    resolvers: {},

    get ( target, property, proxy ) {

        if ( ! target.hasOwnProperty( property ) && this.resolvers.hasOwnProperty( property ) ) {
            // execute the getter for the property;
            target[ property ] = this.resolvers[ property ]();
        }
        return target[ property ];

    },

    set ( target, property, value, receiver ) {

        // if the value is function set as a resolver
        if ( typeof value === 'function' ) {
            this.resolvers[property] = value;
        // otherwise set value to target
        } else {
            target.property = value;
        }
    },

    has ( target, property, receiver ) {
        //true when proxy handler has either a resolver or target has a value;
        return this.resolvers.hasOwnProperty( property ) || target.hasOwnProperty( property );
    }

};

var lazyObject = new Proxy( object, handler );

Now you can use it like this:

'exampleField' in lazyObject; //returns false
lazyObject.exampleField = function(){ return 'my value' }; // add a resolver function
'exampleField' in lazyObject; //returns true
lazyObject.exampleField; //executes your resolver function and returns 'my value'

This example is to demonstrate the working. You can change after your needs.

Here is a fiddle with a demonstration

Wilt
  • 41,477
  • 12
  • 152
  • 203