最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

How do I compute a variable in JavaScript if and only if it is used? - Stack Overflow

programmeradmin8浏览0评论

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 plicated 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 patibility 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.

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 plicated 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 patibility 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.

Share Improve this question edited Apr 24, 2010 at 23:35 Daniel Vassallo 344k72 gold badges512 silver badges446 bronze badges asked Apr 24, 2010 at 5:13 fentfent 18.2k16 gold badges90 silver badges91 bronze badges 2
  • 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 Commented Apr 24, 2010 at 5: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 puted once. – fent Commented Apr 24, 2010 at 5:46
Add a ment  | 

9 Answers 9

Reset to default 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 ment. Use a singleton (I think it's called):

myObject.prop = (function(){ 
                  function someComplicatedComputationThatMayTakeMoreTime() {
                   //your calculations
                  }
                  return { 
                    toString: function() { 
                     return someComplicatedComputationThatMayTakeMoreTime(); 
                    } 
                  } 
                })()

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

  • John Resig: JavaScript Getters and Setters

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

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 remend you to use it for production, unless you have total control on the environment where your application will be used.

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());
}

This is not practical on the web because IE does not support it, but you can look at https://developer.mozilla/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;

I would remend 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.

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.

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 putations.

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
}

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

发布评论

评论列表(0)

  1. 暂无评论