2

I want to have my cake and eat it too: I want to have a method that returns this for chaining when it is bound to an object but returns undefined when it is called with a null|undefined scope. This seems to work fine but if you put it into JSLint then you get a Strict Violation errors. I don't need to use strict mode, but it seems like this should be possible and it works (open the console). Is this okay and/or how else could you accomplish this effect?

var o = {}; // or some pre-existing module
o.meth = (function (undefined) {

    // contrived example where you'd want to be able to locally 
    // access `meth` even if the outer `o.meth` was overwritten

    'use strict';

    var hash = Object.create(null); // "object" with no properties

    // global ref - or `null` where strict mode works
    var cantTouchThis = (function () {
        return this; 
    }).call(null);

    function meth (k, v) {
        var n;
        if (typeof k == 'object') { // set multi
            for (n in k) {
                meth(n, k[n]);
            }
        } else {
             if (v === undefined) { return hash[k]; } // get
             hash[k] = v; // set
        }
        if (this == cantTouchThis) { return; }
        return this;
    }

    return meth;

}());

And if you look in the console:

var localM = o.meth; // now scopeless
console.log(  o.meth('key', 'val')  ); // should return `o`
console.log(  localM('key', 'val')  ); // should return `undefined`
Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
ryanve
  • 50,076
  • 30
  • 102
  • 137
  • Some people say that undefined shouldn't be compared this way (== or ===) as it can be redefined somewhere in Your or third parties code. Which is of course a bad habit to do so. – Piotr Salaciak Aug 29 '12 at 21:54
  • @PiotrSalaciak Yea I was assuming a clean environment for the sake of the question. I just added `undefined` tho as the first param for ya ( which will be `=== undefined` b/c it's invoked with zero params ;) Another safe way to do it is compare `=== void 0`. – ryanve Sep 03 '12 at 10:53

2 Answers2

1

This will almost do what you want.

dontTouchThis will be null in environment that supports "use strict" directive, and will reference global object in those that don't support it.

First of all, you can replace that .call(null) chunk with just (function(){return this})(); it will have more or less the same effect.

Second, don't forget that there are 4 ways function can be called, when it comes to this value: as a method, as a standalone (baseless) function, via call/apply, and with new operator. This means that:

  • new (o.meth) will return o
  • o.meth.call(o2, ...) will return o2
  • o.meth.call(null, ...) will return undefined

Finally, if someone aliases meth to a global variable, then in strict-mode-supporting environment meth() will return global object, not undefined since cantTouchThis (being null) won't be equal to this (which will reference global object).

// in global scope

var meth = o.meth;
meth(...); // `this` references global object, will return global object

(function() {

  // in local scope
  var meth = o.meth;

  meth(...); // `this` is `undefined`, will return `undefined`

})();
kangax
  • 38,898
  • 13
  • 99
  • 135
  • Thanks / yea that helps. I guess in strict mode `root = this || window` is also an option inside a closure, if you know it's being called in the global scope. Or `root = (function(){return this}) || window`. Or save separate refs to the root `this` and to the `window` to be safe. You can see what I was working on @ https://github.com/ryanve/hook – ryanve Sep 03 '12 at 10:46
0

To be redundantly safe, I ended up using a local function for "grounding" scope resolution:

var gnd = (function () {
    var globe = this || window;
    return function (o) {
        // for grounding (securing) scope
        return o == null || o === globe ? 0 : o;
    };
}());

See usage in the source here.

ryanve
  • 50,076
  • 30
  • 102
  • 137