0

I've constructed a rather useful function to identify data-types; however, while coding happily I was rudely interrupted with a rather worrying dilemma.

As you may know, after calling .bind({foo:'bar'}) on a closure, you cannot access said foo property "externally"; however, inside the closure, this.foo works.

Also, when assigning something in such a way, you often face a throw: intermediary ... blah blah is undefined when you try access a property - directly after defining it. The code below fixes these issues but...

The problem is explained after the code:

"use strict";

if ('undefined' == typeof global)
{
    Object.defineProperty
    (
        window,'global',
        {
            writable:false,
            configurable:false,
            enumerable:false,
            value:window
        }
    );
}


Object.defineProperty
(
    Function.prototype, 'wrap',
    {
        writable:false,
        enumerable:false,
        configurable:false,

        value:function(jsob)
        {
            this.bind(jsob);

            for (var i in jsob)
            { this[i] = jsob[i]; }

            return this;
        }
    }
);


global.typeOf = function(data)
{
    if ((data === null) || (data === undefined))
    { return 'void'; }

    if ((data === true) || (data === false))
    { return 'bool'; }

    var tpof = (({}).toString.call(data).match(/\s([a-zA-Z]+)/)[1].toLowerCase());

    if ((tpof == 'array') || (tpof == 'htmlcollection') || (tpof == 'namednodemap'))
    { return 'list'; }

    if ((tpof == 'global') || (tpof == 'window'))
    { return 'glob'; }

    switch (tpof.substr(0,6))
    {
        case 'number': return 'unit';
        case 'string': return (/[^\x20-\x7E\t\r\n]/.test(data) ? 'blob' : 'text');
        case 'object': return 'jsob';
        case 'functi': return 'func';

        default: return 'node';
    }
}
.wrap
({
    list:'void bool unit text blob list jsob func node glob'.split(' '),
    init:function()
    {
        this.list.forEach(function(item)
        {
            global[(item.toUpperCase())] = item;
            global[('is'+(item[0].toUpperCase() + item.substr(1,item.length)))] = function(data)
            {
                return ((typeOf(data) == this.text) ? true : false);
            }
            .bind({text:item.toLowerCase()}); // <-- ISSUE
        });

        return this;
    }
}).init();

So the little wrapper above takes care of such weirdness; however, have a look on the line where <-- ISSUE is; see, I cannot use wrap() there, I have to use bind(), else - inside the function - this is undefined!!

Let me clarify: If you use the entire code just as it is above in <script> tags inside a brand-spanking-new html file; just change that ISSUE line's bind word to: wrap; then try something like: isText("bite me!");

You will see an error that specifies something like:

cannot read property "text" from undefined ..

so; if you do a console.log(this) inside that function definition there; you will see undefined.

If anyone could help fixing this, or at least explain why this is happening, I'd really appreciate the input.

Evan Trimboli
  • 29,900
  • 6
  • 45
  • 66
  • @Kashif :: The `wrap()` method makes it possible to access properties -external from the closure. It shortens the code needed also. –  May 10 '16 at 02:29
  • @deceze :: all the code may be part of the issue; and - the code is usable just as it is, just copy+paste & test; nothing is "missing" at all. –  May 10 '16 at 02:31
  • 1
    Seems like the problem you're running into is the same as the one you're trying to fix. The wrapped isX functions cannot access `this` from the wrapped closure. I suspect you're in effect asking for an explanation of your "wierdness" – Tibrogargan May 10 '16 at 03:44
  • @Tibrogargan :: haha, well spotted, but, still though, can **this** be fixed? I mean, by **this** being _undefined_ in its own scope - or to specify its scope rather. –  May 10 '16 at 03:49
  • 1
    @argon Maybe you could explicitly specifiy it somehow - but I have no idea, I'm out of my depth – Tibrogargan May 10 '16 at 03:55
  • I think you did not understand how `bind` works. That `this.bind(jsob);` call does not make any sense, as you are not using its result. Also it doesn't have anything to do with closures. – Bergi May 10 '16 at 06:45
  • WTH are you using this `wrap` thing at all? In the example you've shown, the same result could much more easily have been achieved without a custom Function method. – Bergi May 10 '16 at 06:49
  • @Bergi :: If that could fix the issue, by all means, your answer would be appreciated ;) –  May 10 '16 at 08:16
  • Whatever else you are trying to do here, it is certain that `this.bind(jsob);` is a NO-OP. Also, you are misusing the word "closure"--you seem to be using it to just refer to a function. –  May 10 '16 at 17:38

1 Answers1

1

I see absolutely no purpose for this wrap function. In fact there's no reason to use this or bind at all for this use case. Just do

global.typeOf = function(data) {
    if (data == null) return 'void';
    switch (typeof data)
        case "boolean": return 'bool';
        case "number": return 'unit';
        case "string": return /[^\x20-\x7E\t\r\n]/.test(data) ? 'blob' : 'text';
    }
    switch (Object.prototype.toString.call(data).slice(8, -1).toLowerCase()) {
        case "array":
        case "htmlcollection":
        case "namednodemap": return 'list';
        case "global":
        case "window": return 'glob';
        case "object": return 'jsob';
        case "function": return 'func';
        default: return 'node';
    }
};
global.typeOf.list = 'void bool unit text blob list jsob func node glob'.split(' ');

global.typeOf.list.forEach(function(item) {
    global[item.toUpperCase()] = item;
    global['is'+item[0].toUpperCase()+item.slice(1)] = function(data) {
        return typeOf(data) == item;
    }
});
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • The point of the "bind" (wrap) is that the list of primary types should be contained in a place where it belongs, because I also have another function: "kindOf" - which checks for secondary datatypes and it needs a reference from which to name and define constants and functions accordingly. It can be done by just assigning it after, but I'm looking for a way to assign it once, without having to re-reference "typeOf" outside of it's own scope while defining it. This is because these globals should be immutable after they are defined; the code is longer lol, this is the short version :) –  May 11 '16 at 00:30
  • If you can merge that list with the typeOf closure, in such a way that it can be accessed like: `console.log(typeOf.list)` -- and if your code works in NodeJS also, then I'll gladly accept it. The problem that I have with your current "isFunc" assignment is that the comparison checks for a variable "item" that may not exist in scope at a time the "isFunc" is called, hence, that is also the reason for "bind" as it contains its own value. –  May 11 '16 at 00:43
  • Found some interesting posts on "lexical scope" as you used it: https://developer.mozilla.org/en/docs/Web/JavaScript/Closures and .. here: https://spin.atomicobject.com/2014/10/20/javascript-scope-closures/ .... it could cause problems if not carefully used though... have a look here: https://www.toptal.com/javascript/10-most-common-javascript-mistakes –  May 11 '16 at 01:11
  • Anyway, I'm sold, it works, thanks for participating :) –  May 11 '16 at 01:20
  • "*without having to re-reference "typeOf" outside of it's own scope while defining it, because these globals should be immutable*" doesn't make any sense. You can create an immutable global value, and nothing keeps you from referencing it afterwards (that's actually their whole purpose) – Bergi May 11 '16 at 01:20
  • "*The problem with your current "isFunc" assignment is that the comparison checks for a variable "item" that may not exist in scope at a time the "isFunc" is called*" is rubbish (sorry for the harsh words). This is exactly what [closures](http://stackoverflow.com/q/111102/1048572) are good for. – Bergi May 11 '16 at 01:24
  • I meant to say that once it is defined in one statement (i.e. no semi-colon in between statements) - it should be "frozen" and not be changed or deleted by any other code. Trying to establish a strict code policy and ways to work that would reduce creation of bugs, or instability. –  May 11 '16 at 01:25
  • At the "rubbish" comment, ya, I saw that as I was researching on this. It's great that it's working everywhere now as a standard for some years lol, I guess I didn't get the memo :D –  May 11 '16 at 01:27
  • @argon: You can put an `Object.freeze(global.typeoOf)` after the last of the statements that create the object :-) Or otherwise use an IIFE to wrap it in a clear building block (like a module). Or if you really want to use a custom `wrap` function, just let it extend the object with properties, but I still don't see a reason to `bind` it to anything. Regardless, that `init` thing as a method should be avoided. – Bergi May 11 '16 at 01:29
  • Thanks for the update, see, that's what I'm trying to eliminate: **duplication** ... in your updated code: `global.typeOf` is referenced 3 times. It can be done 1 time, if you use that `wrap` thingy-mabob –  May 11 '16 at 01:29
  • :: yep, I use "Object.freeze", lol, even made a "deepfreeze" :D –  May 11 '16 at 01:33
  • I think you really should have a look at the various kinds of module patterns then (none of which need a `wrap` thingy). Especially helpful if you actually don't need the `list` to be available globally, but rather only in multiple functions (`kindOf` etc). – Bergi May 11 '16 at 01:34
  • Good points, thanks, I'll look into it. Right now I'm trying to establish a small yet powerful starting-point as 1 JS file that could be shared cross platform (browser + nodejs), anyway.. thanks for the great advice. –  May 11 '16 at 01:39