3

I am wondering if it is possible to do something like the following:

var obj = {
   counter: (function(){
                if(!this.val){ this.val = 0; }
                this.val += 1;
                return this.val;
            })();
};

console.log(obj.counter); //should output 1
console.log(obj.counter); //should output 2
console.log(obj.counter); //should output 3
...

Is there a way to get a field from an object like this such that it re-evaluates a function each time it is accessed?

3
  • Why don't you use a function instead of a field Commented Jun 26, 2014 at 15:44
  • Counter is being defined as an IIFE, so your reference to this is lost to the IIFE itself. Thus every time you call obj.counter, this.val will be undefined, and thus set to 0, then 1. @ksven beat me to the answer code, so am posting as a comment. Commented Jun 26, 2014 at 15:47
  • alright, can I put the actual counting variable elsewhere and still get the auto incrementing? Commented Jun 26, 2014 at 15:48

4 Answers 4

9

You can use a getter:

var obj = {};
Object.defineProperty(obj,"counter",{
    get: function() {
        this.val = this.val || 0;
        this.val++;
        return this.val;
    }
});

console.log(obj.counter); // 1
console.log(obj.counter); // 2
console.log(obj.counter); // 3
Sign up to request clarification or add additional context in comments.

1 Comment

This is great, I didn't know it existed. Thanks!
4

This is possible with proxies if your target platform supports them:

var obj = Proxy.create({
    get: function(target, value) {
        if(value == 'counter')
            return this.val = (this.val || 0) + 1;
    }
});

console.log(obj.counter); //should output 1
console.log(obj.counter); //should output 2
console.log(obj.counter); //should output 3

Another option would be a getter:

obj = Object.create({}, {
    counter: {
        get: function() {
            return this.val = (this.val || 0) + 1;
        }
    }
})

or a valueOf object (this doesn't work with console.log, but does with arithmetic):

var obj = {
    counter: {
        valueOf: function() {
            return this.val = (this.val || 0) + 1;
        }
    }
};

console.log(obj.counter+5); // 6
console.log(obj.counter+5); // 7
console.log(obj.counter+5); // 8

Comments

0

Using Proxy class as well as use case for evaluate requrements for property evaluation

/* https://github.com/hack2root/lazyeval */

let lazy = (eval) => ((data) => new Proxy(data, {
  set(obj, key, val) {
  obj[key] = val;
  eval(obj);
  }
}))({});

// 1. ARRANGE
let a = 1;
let b = 2;
let c;

// 2. ACT
let func = lazy((f) => {
  if (f.a && f.b) { 
    c = f.a + f.b 
  }
});

func.a = a;
func.b = b;

// 3. ASSERT
console.log("c is", c);

let lazy_require = (requre) => (eval) =>  ((data) => new Proxy(data, {
    set(obj, key, val) {
        obj[key] = val;
        if (requre(obj)) {
            eval(obj);
        }
    }
}))({});

// 1. ARRANGE
let a_f = 1;
let b_f = 2;
let c_f;

// 2. ACT
let func_reqire = lazy_require((f) => f.a && f.b);

let lazy_func = func_reqire((f) => {
  c_f = f.a + f.b
});

lazy_func.a = a_f;
lazy_func.b = b_f;

// 3. ASSERT
console.log('c_f is', c_f);

let lazy_require_data = (requre) => (eval) => (data) => new Proxy(data, {
  set(obj, key, val) {
    obj[key] = val;
    if (requre(obj)) {
      eval(obj);
    }
  }
});

// 1. ARRANGE
let a_data = 1;
let b_data = 2;
let c_data;

// 2. ACT
let func_require_data = lazy_require_data((f) => f.a && f.b);

let func_data = func_require_data((f) => {
    c_data = f.a + f.b
});

let func_json = func_data({
    a: a_data,
    b: b_data
});

func_json.a = a;
func_json.b = b;

// 3. ASSERT
console.log('c_data is', c_data);

Comments

0

Generator

const obj = {
  counter: function* () {
    let val = 1;
    while (true) yield val++;
  }
};

const foo = obj.counter()
const bar = obj.counter()

console.log(foo.next().value); // 1
console.log(foo.next().value); // 2
console.log(foo.next().value); // 3

console.log(bar.next().value); // 1
console.log(bar.next().value); // 2
console.log(bar.next().value); // 3

You can also initialize the generator as an IIFE to prevent multiple counters per instance of obj.

const obj = {
  counter: (function* () {
    let val = 1;
    while (true) yield val++;
  })(),
};

console.log(obj.counter.next().value); // 1
console.log(obj.counter.next().value); // 2
console.log(obj.counter.next().value); // 3

1 Comment

this answer can use a bit of explanation to the code. Just to help future readers.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.