Skip to content

Instantly share code, notes, and snippets.

@YawarRaza7349
Last active August 29, 2015 14:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save YawarRaza7349/70d5ad0b06aa401d4f32 to your computer and use it in GitHub Desktop.
Save YawarRaza7349/70d5ad0b06aa401d4f32 to your computer and use it in GitHub Desktop.
7-16-14-1

The only reason you are able to call Function.prototype.snapshotFunction as Function.snapshotFunction is because Function is itself a function, as are Object, String, Number, and any other JavaScript "class". If you replaced Function with String, this would no longer work. You added a method to all functions and called it on the function instance Function. So if you tried using this, it would reference the built-in Function function. So yes, this should either be a free-standing function, or, if you wanted to make it a method (e.g. getPrimitive.snapshot(["testPrimitive", testPrimitive])), then remove the first parameter of the function, and replace all references to it in the function with this. Speaking of this, I can't think of all the use cases for your function involving this, but with code like this, it's pretty likely this will cause something to break. I found an example here.

IMO, the [string, value, string, value, ...] thing is bad API design, and doesn't match the intended semantics of an array/list/collection (then again, neither does using them as tuples, but I'm pretty sure I've seen that before, sigh...). Some alternatives: preferably [{name:theName, value:theValue}, ...], but you can also do (because your keys are strings) [{theName:theValue}, ...], or even [[theName, theValue], [theName2, theValue2], ...] (so succumbing to using arrays as tuples) would be better. This is more of an opinion, but I'm sure I'm not alone in thinking this.

Also, out of curiosity, did you want the animals' sounds property to be able to contain objects as well as arrays? Because, you know for(var k in obj) iterates through obj's keys (viewing it as an object), rather than what C#'s foreach does, right?

If you wanted to iterate over an arbitrary object's keys, then you can do what Scott suggested, although I prefer a more inline approach:

for(var s in animal.sounds) {
    (function(sound) {
        myAnimal["say" + animal.sounds[sound]] = function() {
            console.log(animal.sounds[sound]);
        }
    })(s);
}

Another way to write it (what I would've done at first, although it's basically exactly the same as the above as far as I can see, the above might be better practice though, not sure about this myself):

for(var s in animal.sounds) {
    (function() {
        var sound = s;
        myAnimal["say" + animal.sounds[sound]] = function() {
            console.log(animal.sounds[sound]);
        }    
    })();
}

In this case, you could've even done:

for(var s in animal.sounds) {
    (function(soundVal) {
        myAnimal["say" + soundVal] = function() {
            console.log(soundVal);
        }
    })(animal.sounds[s]);
}

If you wanted to iterate over an array, then it's easier:

// since forEach takes a function, which creates a new scope, you don't have to do it in addition to for's code block (which does not create a new scope in JS)
animal.sounds.forEach(function(soundVal) {
    myAnimal["say" + soundVal] = function() {
            console.log(soundVal);
        }
});

You can also use forEach with an object and its keys using Object.keys(obj) (it's a bit different than what for...in iterates through though).

So that takes care of the iteration example. In the demo code you gave with your function, you can do the same thing (in JS parameters are pass reference by value, and same with closures as far as I know, so object mutation is externally visible, while variable reassignment is not, which makes your last example work also).

@danShumway
Copy link

Yeah, the general consensus seems to be that callbacks are a more elegant way of getting the same behavior, which I do agree with - it's basically the same thing as what I'm doing with less overhead. My code just uses eval to create a new variable/function in an isolated scope and return that function, rather than doing that inline.

I suppose one advantage of this is that I can go back to existing functions and update the variables they're referencing:

function AnimalFactory(_json) {
    var myAnimal = {}; myAnimal.name = _json.name;

    for(var s in _json.sounds){
        var mySound = _json.sounds[s];
        myAnimal["say"+mySound] = Function.snapshotFunction(function(){
            console.log(mySound); }, ["mySound", mySound]);
    }

    myAnimal.mutate = function(funcString, func){
         myAnimal[funcString] = Function.snapshotFunction(func, ["mySound", "Allen!"]); 
    }

    return myAnimal;
}


var myCat = AnimalFactory(cat);
myCat.sayhiss(); //
myCat.mutate("sayhiss", myCat.sayhiss);
myCat.sayhiss();

But that's reaching pretty far, and is more than offest by some of the problems with using this implementation, like not having access to variables that aren't deliberately passed in (attaching the code to Function isolates it from any other variables that would otherwise be farther up the scope chain). Practically, inline callbacks should handle pretty much any scenario where you'd want to use something like this.

I'm less concerned with passing in standard arrays for the data - it was something I thought about when writing the code, but for the purposes of a jsfiddle, making an array of objects to handle that value pair or using arguments with objects to eliminate the array entirely just increases the amount of code and makes it more cumbersome to use the function. In a production environment, I don't advise using multitype arrays, but in a tech demo, javascript does have multitype arrays, and I consider them to be useful. But that's just a personal stylistic opinion, I do realize it tends to rub people the wrong way and I apologize about that.

I would though argue that the this breakage you link to is actually working as fully intended. The method creates a variable in the current scope and keeps it in that scope for all future calls: so if snapQuack() started referring to a different location for this than its actual location when passed in, I would have some problems.

That is, as far as I can tell, expected behavior for all callbacks, not just my implementation: http://jsfiddle.net/danShumway/97QJj/3/

@ScottHamper
Copy link

Hey guys,

I don't want to get too pedantic or anything, but I just had some final thoughts.

First, although you certainly could collapse my makeSayFunction to be an inline immediately invoked function expression, it's less "functional" to do so (as in functional programming). In my original example, makeSayFunction was inside of the AnimalFactory scope, invisible to the outside world, but if you were writing a legit application, you'd probably want to expose it in some way so that you could unit-test it.

Collapsing it into an inline IIFE makes the core functionality a lot more annoying to unit test as now you've got side-effects and dependencies on AnimalFactory to worry about. makeSayFunction is just a simple "here's some input, here's some output" function that doesn't produce any side effects.

Further along those lines - Dan, here's how you could still handle the situation in your previous comments code block without using your snapshot function, http://jsfiddle.net/97QJj/4/. Realistically, this sample is poorly organized (and we've just polluted the global object with a bunch of stuff), but it's the general idea that I'm trying to get across.

Lastly, a "callback" is a function that is passed as an argument into another function, in which that function eventually calls the passed-in function. So:

var callTheCallback = function (callback) {
    callback(); // This is a "callback" purely because it is passed as an arg and we've called it
};

None of our code samples actually have any callbacks in them, :P.

@YawarRaza7349
Copy link
Author

Dan: Yeah, I see what you mean about this.
Scott: That's a good point, I didn't think about it from a unit testing point of view.

@danShumway
Copy link

Scott: Thanks for the clarification - my understanding was that a callback just referred to a wrapper around a specific function, regardless of what parameters it took: it's good to figure out the technical definition.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment