1

I'm having troubling understanding the point of the variable tmp in the following code:

$.extend($.Widget.prototype, {
    yield: null,
    returnValues: { },
    before: function(method, f) {
        var original = this[method];
        this[method] = function() {
            f.apply(this, arguments);
            return original.apply(this, arguments);
        };
    },
    after: function(method, f) {
        var original = this[method];
        this[method] = function() {
            this.returnValues[method] = original.apply(this, arguments);
            return f.apply(this, arguments);
        }
    },
    around: function(method, f) {
        var original = this[method];
        this[method] = function() {
            var tmp = this.yield;
            this.yield = original;
            var ret = f.apply(this, arguments);
            this.yield = tmp;
            return ret;
        }
    }
});

Why not simply using a function-local variable var yield and completely leaving out tmp in around method? What purpose does it serve? Is this a common design pattern?

Thanks for some hint.

Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
Jinghui Niu
  • 990
  • 11
  • 28
  • temporary. I guess in `f()`, `this.yield` is used which should refer to `original`. – Tushar Mar 22 '17 at 07:34
  • yes, you could use a var named `yield` ... except `yield` is a (reserved) keyword - the name of a var has no significance – Jaromanda X Mar 22 '17 at 07:34
  • maybe the this.yield=original is use in the f.apply() function. that's why this.yield data was stored in a temporary variable. – Chinito Mar 22 '17 at 07:37
  • Why don't you add to your question the way you would write the around function? It will make it easier to point out any differences. – GertG Mar 22 '17 at 08:08
  • Regarding the former `AOP` tag, wrapping and reassigning already declared functionality (be it functions or methods) misses any aspect of _AOP_. Any language which wants to qualify for the latter has to provide abstraction levels for at least `Joinpoint`, `Advice` and `Aspect`. The use case described by the OP should be referred to as method modification, and JavaScript of cause is well suited for this scenario and could easily provide a complete `target`/`context` aware toolset of method modifiers like `around`, `before`, `after`, `afterThrowing` and `afterFinally` via `Function.prototype`. – Peter Seliger Sep 14 '22 at 17:12

1 Answers1

0

Apparently that code comes from Extending jQuery UI Widgets, which provides some useful context. That article refers to Avoiding Bloat in Widgets as the source for the code. However, the original doesn't have the tmp variable.

I like the addition of tmp, because it avoids a side-effect that the original code has. Consider how the around function is supposed to be used:

YourWidgetObject.existingWidgetClass("around", "click", function(){
    console.log("before"); //or any code that you want executed before all clicks
    yield(); // executes existing click function
    console.log("after"); //or any code that you want executed after all clicks
}

This will work as expected with or without the tmp juggling. The click event will now execute your extra code before and after the event.

But if you don't restore yield using tmp, the object's public method yield will now be redefined. So if someone would have the strange idea of just calling YourWidgetObject.yield() after using around, it would execute whatever existing method around has last been applied to (in this case, click).

Added after request for clarification:

Imagine that you don't restore yield at all, nor set it to null. And after the code above, you do this:

YourWidgetObject.existingWidgetClass("before", "focus", function(){
    yield(); // executes existing **click** function
}

yield now executes the click function on focus, which is very unexpected behavior.

Could you just set yield to null instead of restoring it with tmp? Sure, as it is now, it won't make a difference. But as you might change or add other methods, it's more prudent to make the around method unaware of the current state, i.e. it shouldn't have to know that yield is always null when it gets called.

As a sidenote, I think this is a terrible around method, since putting things around an event is not necessarily what it does - you can call yield however many times you want, or not at all. More sensible for a real around method would be to accept two callbacks, one to be executed before the event and one after. That way you don't need to expose a yield method in the first place.

around: function(method, before, after) {
    var original = this[method];
    this[method] = function() {
        before();
        original();
        after();
    }
} 
GertG
  • 959
  • 1
  • 8
  • 21
  • I read your answer again and again and still think that `tmp` unnecessary. The `this.yield` starts with `null` and will always be restored to `null`, how can someone having a strange idea and just call it? Could you give a example to illustrate that scenario please? Thanks. – Jinghui Niu Mar 23 '17 at 05:13
  • Excellent judgement. It really is leaky abstraction for such an `around` wrapper. – Jinghui Niu Mar 24 '17 at 10:38