2

Flubbing around the revealing module pattern with ES6 syntax, the state of default variables seems dependent on including the trailing () (invocation) on the function.

But, wondering about accessing those underscore variables in the body of the function, some trippy stuff seemed to go on in several different variations.

Investigating some more through several half-baked, half-remembered iterations of the RM pattern; whether it had to fully be an IIFE, or could just stand alone started it off, but then it morphed into looking at how _x, _y, and _z might be accessible.

The error goes away when the trailing () is added, but the behaviour of _._x, accepting a value, that remains. Should it?

Under all this, can default values be added to the pattern using a variation of the ES6 syntax?

const _ = function (_x = 'fish', _y = 'chicken', _z = 'beef') {
    
    // console.log(_x)
    _x
    _y
    _z

    return {

        // _x,
        // _y,
        // _z,
        get x() { return this._x },
        set x(value) { this._x = value }

    }
    
}() // if you invoke it isn't broken per se., but what is the state of revealing module vars?

// _()

let x 
_._x = 'fish-ly' // strongly discouraged, but why does it sort of work?
console.log(x)
x = 'tartwell'
console.log(x)
console.log(_._x) // supposed to be private?
console.log(_._x)

x = 'o.m.g'
console.log(x)
console.log(_._x) // gives fishly

console.log(_.x) // no change but gives undefined
console.log(_.x) // no change but gives undefined
_.x[_._x] // TypeError: Cannot read property 'fishly' of undefined
console.log('x', _.x) // with call () trailing function reinstated log shows x was assigned in the statement above


// _.x = 'leafy greens'
console.log(_.x)

Remaining Question: When _.x[_._x] fires do we get a value for _.x? From the discussion below, the object appears to have taken on a property. But this syntax is not a complete assignment, and if it were it would be a value from the right side. What's happening here?

Tim Pozza
  • 498
  • 6
  • 9

1 Answers1

2

You've defined _ as a function. Functions, being objects, can have arbitrary key-value pairs assigned to them (unfortunately). So

_._x = 'fish-ly'

will assign a property to the _x property of the single function object (viewable by anything that wishes to reference _._x). The closure-scoped variables (not the properties) _x, _y, _z are still properly encapsulated (they'll only be changeable and viewable by what your _ function deliberately exposes), but they won't be created until the _ function gets called.

In your snippet, you've never invoked _, so for a clearer understanding of what's going on with the exact same results, you may as well replace _ with an empty object named {}:

const obj = {};

let x;
obj._x = 'fish-ly' // strongly discouraged, but why does it sort of work?
                   // because it's just a key-value pair on an object
console.log(obj._x) // which are public

console.log(obj.x) // no change but gives undefined
                 // because the only property is "_x", not "x"

Once you invoke the function, the closure variables which will have just been created will be protected, as desired.

Remaining Question: When _.x[_._x] fires we get a value for _.x. From the discussion below, the object appears to have taken on a property. But this syntax is not a complete assignment, and if it were it would be a value from the right side. What's happening here?

Standalone expressions that don't appear to do anything are permitted. For example:

const obj = { prop: 'val' };

obj.prop;
NaN;
window.location;

The last 3 lines don't do anything useful, but the interpreter does parse them as expressions (before discarding them, because nothing is being done with them). Similarly, with the standalone line

_.x[_._x]

the interpreter attempts to parse this as an expression. If it can be evaluated as an expression (and said parsing doesn't throw an error), then the statement on that line will be considered to be complete and valid (despite being useless), and the interpreter will move on to the next line.

But evaluating _.x[_._x] as an expression fails, because it is equivalent to:

_.x[_._x]
_.x['fish-ly']
// _.x is undefined, so:
undefined['fish-ly']

And trying to access any property of undefined will throw:

undefined['fish-ly']

As you can see, the error message is identical to the one in your snippet.

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • OK, that really helps me understand. Can you explain why, following an assignment to ```_._x``` the first ```console.log(_._x)``` returns the assigned value, but subsequent of the same return ```undefined```? A value, however, is revealed by ```_._x[_._x]``` in the TypeError message. I get that it could simply be a matter of not doing what the object expects, but if, as you say, that's what objects do, my feeling is this could be a bad corner case... Thoughts? – Tim Pozza Aug 09 '19 at 08:15
  • 1
    (1) Because, like the snippet says: it gives undefined `because the only property is "_x", not "x"` (if you had assigned to `_.x` instead of to `_._x` you would've seen something when checking `_.x`) – CertainPerformance Aug 09 '19 at 08:16
  • It's my own eyes that need to be debugged. You make for a good optician... – Tim Pozza Aug 09 '19 at 08:18
  • 1
    (2) `_.x[_._x]` evaluates to `_.x['fish-ly']` - look up the `x` property of `_`, THEN look up the `fish-ly` property on that. But the `x` property of `_` is undefined, hence the error `Uncaught TypeError: Cannot read property 'fish-ly' of undefined`. All these underscores make things hard, I'd recommend avoiding them. I don't think they ever add anything useful to readability – CertainPerformance Aug 09 '19 at 08:22
  • Still, fixing the trailing call ```()```, not to waste your time, I hope, if you ```console.log('x', _.x)``` following the partial ```_.x[_._x]``` it looks like there is an ```x``` on the object. The object must be very sticky... Which you address, above... treating it as an expected occurrence. Thanks for looking at my mistaking-through-this-as-learning-technique, Very helpful. – Tim Pozza Aug 09 '19 at 08:24
  • 1
    It *tries* to look up the `x` property on the object, but `_.x` evaluates to `undefined`. That isn't what throws the error. The problem is trying to *look up* a property on `undefined`. `_.x[_._x]` evaluates to `_.x['fish-ly']` which is `undefined['fish-ly']` which throws. – CertainPerformance Aug 09 '19 at 08:28
  • I appreciate that, your contribution to clarity has not gone unnoticed. However, as the question has gracefully come to its final point, and that point was mentioned in the original and in the messages between us, it has been highlighted on the question. What of the assignment of ```x``` onto the object via destructuring? – Tim Pozza Aug 10 '19 at 02:43
  • There's no destructuring going on. The object never gets anything assigned to its `x` property, so accessing the `x` property results in `undefined`; `_.x[_._x]` is `undefined[_._x]`, and looking up any property of `undefined` throws – CertainPerformance Aug 10 '19 at 03:09