The magic of JavaScript's apply()
I recently answered a Stack Overflow question re: how to call a function in PHP with a dynamic number of arguments. The answer is call_user_func_array().
I mentioned in passing that the JS equivalent was apply(), and that apply() could also fake execution context. asked me to elaborate, so here I am.
Meet apply()
apply() is the clever gem of intermediate-advanced JavaScript, and is one of my favourite features of the language.
Inherited by every function/method, apply() has two uses, allowing you to:
- set the this context in which a particular function/method executes
- pass an array of arguments to a function that ordinarily expects each argument to be passed separately
Both of these are hugely useful and allow you to accomplish things that would otherwise require cumbersome loops or outright code re-writes.
Below I'll look at four varying use-cases for apply().
Use case 1: library APIs
Whilst 'faking' the context of a function may seem somewhat whorish at first, it can be hugely useful where libraries are concerned. Why, we need look no further than our ubiquitous friend jQuery. Consider this:
1var long_paras = $('p').filter(function() {
2 return $(this).text().length > 5;
3});
There, I select all paragraphs with text longer than 5 characters. (The filter() method, if you don't know, takes a callback function and applies it iteratively to each matched element; the element is removed from the stack if the function returns false).
But have you ever wondered why, conveniently, this inside the callback magically points to the element? There's certainly no native reason this should be so. The reason is, internally, jQuery does something like this: (Warning - guesswork ahead, but the point remains).
1jQuery.prototype.filter = function(func) {
2
3 //for each element on the stack
4 for (var i=0, len=this.elements.length; i<len; i++)
5
6 //apply the callback function in context of element
7 if (!func.apply(this.elements[i]))
8
9 //if func returns falsy, remove element from stack
10 delete this.elements[i];
11};
As you can see, the callback is not merely invoked, it is applied, with each element (in turn) set as the this context. Clever, eh? If this wasn't possible, jQuery would have to pass the element as an argument to your callback instead - decidedly less graceful. The callback is all about the element, so it's sensible this points to it, just as it does natively in, say, event callbacks bound with addEventListener().
Use case 2: array as arguments
This is easily demonstrated with some built-in methods, for example Math.min(). This method, you won't be staggered to learn, finds the minimum value from within a list of numbers passed as separate arguments.
Math.min(2, 5, 7); //2
That's fine, but the chances of you ever wanting to use this method in the above way are pretty slim. Almost always, you'd have an array of numbers, probably gathered or computed dynamically, and want to find the lowest one from within that.
Other languages, such as PHP via array_min, allow this. In JS, you'll need apply() magic:
1//gather up some random numbers into an array
2var random_nums = [], num_nums = 50;
3for (var i=0; i<num_nums; i++)
4 random_nums.push(Math.floor(Math.random() * 10000));
5
6//find lowest, with some apply() fudgery
7Math.min.apply(null, random_nums);
Since our numbers there are generated dynamically, the first approach is not an option. Because apply() takes an array and passes it to a function as though each value of that array was a separate argument being passed, this is our solution.
Note that I pass null as the first argument, since Math.min() is a static method that does not care about context - it merely deals with what it is passed as arguments.
Let's look at an example where contexts is relevant.
Use case 3: an approach to inheritance
JavaScript lacks a formal notion of classes and, therefore, classical inheritance. What it does have, and what is at the centre of the language, is prototypal inheritance. Using this, there are various approaches to simulating traditional inheritance.
One of them (though not necessarily the best one) utilises apply().
1function Parent() {}
2Parent.prototype.sayHi = function() { alert('hi'); };
3function Child() { Parent.apply(this, arguments); }
4Child.prototype = new Parent();
As I say, this is just one approach to inheritance simulation. Like all approaches, it has advantages and disadvantages. It would be off-topic to discuss them here, but there's more info on this here. Ultimately, the approach to inheritance used comes down to taste and requirement a lot of the time.
Use case 4: Converting function arguments to array
You might think this sounds like the second use-case I demonstrated, but I'm driving at something rather different here.
Inside any function, a local variable, arguments, is implicitly created. It is an array-like object (but NOT an array) of the arguments passed to that function.
1function someFunc() { console.log(arguments); }
2someFunc('apple', 'pear');
There, arguments looks and feels like a normal, indexed array - in fact it's an object, with keys named 0, 1 and 2.
A common need is to treat arguments as an array - and, happily, apply() can help us with this.
(Well, it's part of the help. The following technique also owes a lot the fact that Array's slice method is not particularly fussy about needing a real array - apparently an array-like object will do fine, also.)
1function someFunc() {
2 arguments.push('banana'); //ERROR
3 var args_as_array = [].slice.apply(arguments);
4 args_as_array.push('banana'); //OK
5}
6someFunc('apple', 'pear');
Line 2 fails because push is a method of Array, not Object. We must first 'coerce' the arguments object to an array, as we do in line 3. Normally, slice() is concerned with the extraction and return of certain elements of an array, but since we don't pass it any arguments denoting this, it simply gives us back an array in its entirety.
apply() vs. call() vs. bind()
These three tend to get lumped together because they're all concerned with setting the this context in which the stipulated function runs. Let's quickly look over some of the differences.
call() works just like apply() except arguments must be passed individually, not as an array. So it has only one benefit - context setting. With this in mind, you'll probably find yourself using apply() far more than call().
bind(), on the other hand, is concerned with the creation of new functions that are permanently bound to a particular context. Contrast this with apply(), which does not create functions - it merely invokes them.
So if you wanted to make a permanent copy of a function or method, but which runs in a different context from its original, use bind().
This is best demonstrated with reference to a common error among early JavaScript developers. Perhaps armed with the knowledge that complex types are copied by reference, not value, the following does not behave as they expect:
1var obj = {
2 property: "hello",
3 method: function() { alert(this.property); }
4}
5
6//make a shortcut to the object's method
7var alias = obj.method;
8
9obj.method(); //alerts hello, as expected
10alias(); //alerts undefined - context not preserved
We make a reference to the method - but the context is not stored. Therefore, when the alias is invoked, by the that time it's running in the context of the global scope, not obj.
To make the alias whilst also preserving context, bind() is needed:
1var alias = obj.method.bind(obj);
2alias(); //alerts hello - context preserved
(Sidenote: another capability of bind() is that you can specify default values for function arguments, a la partial application, but again that's off-topic for this post. For more on this very useful method, check out the excellent MDN documentation on it.)
And finally... apply() with instantiation
One of the minor drawbacks of apply() is that it cannot natively be used for instantiation. You might try something like this:
1function Dog(name, colour) {}
2var fido = new Dog.apply(null, ['Fido', 'brown']);
What we meant was to run apply() on the instantiation of the class. What actually happens, though, is JavaScript first runs Dog.apply(...) and then the result of that operation is instantiated. In short, a right mess, and not what we wanted.
Fortunately there's a couple of workarounds for this. I favour this one, demonstrated in this Stack Overflow answer:
1//set up our class and a method
2function Dog(name, colour) {
3 this.name = name;
4 this.colour = colour;
5}
6Dog.prototype.sayHi = function() {
7 alert(this.name+' says hi!');
8}
9
10//make a new version of apply() that supports instantiation
11function newApply(classs, args) {
12 function F() { return classs.apply(this, args); }
13 F.prototype = classs.prototype;
14 return new F();
15}
16
17//instantiate and test
18var fido = newApply(Dog, ['Fido', 'brown']);
19fido.sayHi(); //Fido says hi!
A bonus of that snippet is it demonstrates an alternate approach to simulating classical inheritance (I mentioned there were a few ways). This approach is known as the temporary constructor approach, and utilises a blank function as a 'proxy' between the parent and child classes. As I mentioned, I won't go into these here - but there's more info on them in this article.
Conclusion
So there you have it. I told you apply() was clever. A lot of what we take for granted in library APIs depends its magic. At first the concept can seem quite shocking - faking context? Isn't that bad pattern design? - but as I've hopefully shown, there are good cases for this. And there's more - I showed only a handful.
(Icon attribution: Oxygen Icons)
5 comments | post newREGEX round-up: two issues...
I've recently become something of an addict to answering questions on Stack Overflow. As something of a gleeful REGEX sadist, this is one of the areas I answer on.
Two questions recently highlighted again some of the inabilities in REGEX.
1. Capturing repeating sub-groups
One concerns captured sub-groups. If you're not sure what this means, take a look at this:
"hello, there".match(/hello, (there)/);
The result of that is an array of the following structure:
1[
2 "hello, there", //the whole match is always the first key
3 "there" //...followed by any sub-matches
4]
As the comment says, in most REGEX implementations if not all, the first element of the matches array is the entire match. But I also asked for a sub-match, (there), so that gets sent in as the second array element. And so on, for as many sub-matches as I specified.
This is fine, but gets tricky where you want to repeat a sub-match and capture each repetition of it separately. Consider this:
"foo bar bar bar".match(/foo (bar ?)+/);
That pattern asks for "foo" followed by "bar" one or more times (since I use the "once or more" operator, +, after my sub-group). It returns this:
["foo bar bar bar", "bar"]
You can see the instruction to allow repetition of the sub-group has been honoured insofar as the pattern, in its entirety, matches. But you will also notice that it captured only one of the sub-group instances, not three.
If you're finding this a bit of a headache (and you either love REGEX or find it melts your brain), the bottom line is this:
Your matches array will contain only as many results as there are sub-groups explicitly defined in your pattern (plus the entire match as the first element).
At least in JavaScrtipt and PHP, anyway. I have seen fleeting references to functionality in other languages that can capture repeated - i.e. implied instances of - sub-groups, but I don't know for sure.
Partial workaround
As confirmed by the priceless Regular-Expressions.info, the above pattern is a common pitfall, but I include it just to illustrate the point.
A partial workaround is to capture the repeated sub-group in yet another sub-group. So:
"foo bar bar bar".match(/foo ((bar ?)+)/);
That returns this:
["foo bar bar bar", "bar bar bar", "bar"]
...because this time I made a point of capturing not only the sub-group to be repeated, but also the cumulative result OF that repetition.
But the problem remains that we have not captured each instance separately. When I say "problem", it's not an error of R&G&X; it wasn't designed to do this. It's just an inability.
It's not a show-stopping problem. If you really want "bar" to be represented three times separately in an array, it doesn't take much to first do the REGEX then split the second array element (in our case) by the space delimiter).
2. REGEX is not a parser
Another question that I answered the other day considered the use of REGEX to parse a proprietary tag format.
This should set alarm bells ringing immediately. REGEX is not a parsing tool.
In any case the user wanted to parse the various tags from the following string. The complication is in the fact that tags may contain nested sub-tags.
Hi [{tag:there}], [{tag:how are you?}]. [{I'm good}], thanks.
A similar question came up today, asking the same thing, but with HTML. (Incidentally, REGEX is always a bad choice for parsing XML-based languages, given the DOM route).
The problem with both is this: REGEX cannot hope to reliably know which opening tags correspond to which closing tags.
Take the following HTML (based on the question I mentioned above):
1<table>
2 <tr>
3 <td>Nottingham Forest</td>
4 </tr>
5 <tr>
6 <td>Notts County</td>
7 </tr>
8 <tr>
9 <td>Mansfield Town</td>
10 </tr>
11</table>
Let's say you want to capture the row that contains the string "County":
/<tr\b[^>]*?>[\s\S]*?County[\s\S]*?<\/tr>/gi
Tested against our HTML, this will be the result:
1<tr>
2 <td>Nottingham Forest</td>
3</tr>
4<tr>
5 <td>Notts County</td>
6</tr>
This is logical when you think how REGEXP works. It begins at the start of the string, and is asked to find an opening <
tag. It is then told to allow for zero or more characters of any kind ([\s\S]up to and including the string "Forest".(Side note: if you're wondering, I use [\s\S] rather than . because the latter, though a wildcard character, does not in fact match white-space characters, which is a big deal here as our string is multi-line, and line breaks are spacial characters. The only way to match truly anything is with the former, which literally says "get me all non-space and space characters", i.e. everything.)
This is precisely the problem. In telling it to allow any characters of any kind before the string "Forest", we actually cover from the start of the string (i.e. the first row) into the second.
In other words, our match will be from the beginning to the end of the requested row - never just the requested row.
Ideally we'd limit that first [\s\S] from matching another
A partial workaround
Let's return to the first example, with the proprietary tag format. The aim, of course, is to extract each of the three tags. This question was a PHP one, so I came up with this horror.
1$str = "Hi [{tag:there}], [{tag:how are you?}]. [{I'm good}], thanks.";
2$matches = array();
3function replace_cb($this_match) {
4 global $matches;
5 $this_match = $this_match[0];
6 foreach($matches as $index => $match) $this_match = str_replace('**'.($index + 1).'**', $match, $this_match);
7 array_push($matches, $this_match);
8 return '**'.count($matches).'**';
9}
10while(preg_match('/\[\{[^\[]*?\}\]/', $str)) $str = preg_replace_callback('/\[\{[^\[]*?\}\]/', 'replace_cb', $str);
That actually works. The concept is to first locate and extract all tags that do not contain nested sub-tags, and then work outwards. I'll save you the laborious, line-by-line explanation, but if we print_r($matches), we get this:
1Array
2(
3 [0] => [{tagname:content}]
4 [1] => [{tag2: more data here}]
5 [2] => [{tag1:xnkudfdhkfujhkdjki diidfo now nested tag [{tag2: more data here}] kj udf}]
6)
JSON and PHP: formatting and validating
Formatting JSON to look all lovely
It's always a happy day when I manage to use JSON in PHP. As a JS developer, it sort of feels like I'm marrying the two technologies. Take that, serialised arrays - I'm using JSON.
PHP, of course, has support for encoding and decoding JSON via json_encode() and json_decode() respectively. What it can't do natively, though, is format JSON.
Why would you want to format JSON in PHP to make it look all nice and indented? Well, if the system you're building uses JSON for a config file, and you want users to be able to edit that config file within the system, in a textarea, say.
I then found this function, which largely does the job.
It can, however, get the indentation wrong sometimes. Also, it can leave whitespace at the end of lines. So I extended it slightly. The following three snippets should be inserted just before the final return statement.
Firstly, let's clear any whitespace left at the end of lines.
$result = preg_replace('/\t+\n/', "\n", $result);
Next, let's fix the indentation. What I noticed was that the script was indenting lines containing closing brackets/braces precisely double what it should be - so four tabs instead of two, for example. So, the following halves each case.
1function PR_callback($match) { return substr($match[1], 0, strlen($match[1]) / 2).$match[2]; }
2$result = preg_replace_callback('/^(\t+)(\]|\})/m', 'PR_callback', $result);
Note I'm using a callback on preg_replace() - this gives me greater control over the nature of my replacements. preg_replace() automatically forwards to my callback one argument - an array of the match. As ever, key 0 in the array contains the whole match and any subsequent keys contain any sub-matches my pattern looked for.
Lastly, either the script or (more probably) my hacks above end up killing some of the line breaks, so let's restore them.
$result = preg_replace('/([^\t]+)\t/', "$1\n\t", $result);
Et voila - nicely formatted JSON.
Make sure your JSON's valid
JSON and JavaScript Object Notation are not always the same thing. How so? Well, this is valid JS but INvalid JSON:
1var someObj = {
2 one: 'one',
3 two: 'two'
4}
...because the JSON spec demands that a) property names are quoted; b) property names and strings must be encased in double, not single quotes.
So if you end up with invalid JSON, how will you know? PHP >= 5.3 defines json_last_error(), which returns a flag saying what went wrong.
Confusingly, it returns something even if nothing went wrong - JSON_ERROR_NONE. It does not return false, so it is insufficient to check the validity of JSON with:
1$badJSON = '{one: "one"}';
2$array = json_decode($badJSON, true); //2nd param means we get back an assoc. array, not an object
3if (!json_last_error()) echo "All OK!";
Instead, the last line should be:
if (json_last_error() != JSON_ERROR_NONE) echo "All OK!";
PHP versions prior to 5.3
If you're running PHP prior to v.5.3, you won't have json_last_error(). Instead, you can simply check the truthy/falsy value of json_decode(), so:
1$array = json_decode($badJSON, true);
2if (!$array) echo "Problem!";
Obviously this approach is more crude and won't tell you what went wrong - just that something did.
If you're looking for somewhere to host your PHP, whether Linux or Windows hosting, it always pays to make use of a good web hosting review site to find the right web hosting provider. This is a particularly good one, especially their WordPress hosting search page if youre still on your way finding a suitable home for your WordPress blog.
post a commentOn-the-fly conversion of Flash to image for iOS
In this brave new world of smart phones and tablet PCs, the firm I work for is focusing a lot of energy these days on how to roll out its historical content onto these devices.
The problem is, over the years, we've made a lot of Flash banners. And iOS, the OS behind iPhone and iPad, doesn't support Flash.
I was charged with coming up with a solution. What's a quick way of generating static image equivalents of thousands of Flash banners?
I came up with a solution involving JS and PHP. The concept is this: as users browse our content on Flash-supporting PCs, a secret PHP script is loaded in a hidden iFrame that takes a screenshot of the page (yes, it can do this).
Data regarding the banner's position and dimensions is passed to the script by Javascript in the query string.
Works a treat. Though users don't realise it, they're helping us create a cache of images that are alternatives to Flash banners.
We did hit one snag, though. As I said, JS passes PHP the coordinates of the banner but of course these coordinates are relative to the viewport not the window, but PHP's screenshot is of the window, i.e. toolbars and all.
There is no way in PHP to capture the contents of a window only the window, complete with toolbars and other periphirals.
IE actually provided a solution to this problem. It defines window.screenLeft and window.screenTop respectively, the left and top position of the viewport relative to the window. Hurrah! But no Firefox doesn't define these properties. Other browsers do, but give them other meaning, so no dice.
But there is a solution. A very hacky, almost whorish solution, but it works. I'll write a separate blog post in the next few days detailing the technical steps of this conundrum, in case it helps anyone else.
post a commentHello, world
I'm a London-based Javascript specialist, focussing on native JS, jQuery, XSL and related technologies.
Latest content
My projects/writings
XMLPlayground.com - a sandbox for XML development, including (E)XSLT, XQuery, DTD, Schema and more
- Ten Oddities and Secrets of JavaScript
- Commonly Confused Bits of jQuery
- Image Manipulation with jQuery and PHP-GD
- Get Serious with JavaScript: Learn RegExp (in print copies only)
- Catching Up with jQuery's Newer Versions (submitted for print...)
Archive
-
2012
-
Aug
-
Jul
-
Jun
-
May
-
Apr
-
Mar
-
Feb
-
Jan
-
-
2011
-
Dec
-
Nov
-
Oct
-
Sep
-
Aug
-
Jul
-
Jun
-
May
-
Apr
-
Mar
-
Feb
-
Jan
-
-
2010
-
Dec
- >
-
Nov
-
Oct
-
Sep
-
Aug
-
Jul
-
Jun
- > How to: get the node index of an element
- > Match elements via REGEXP in jQuery
- > GD barchart generator
- > Table sort - now REGEXP friendly
- > UI for Postcode Anywhere address look-up
- > Animated table sort
- > Ajaxify - re-route your forms via AJAX
- > curveMe - cross-browser curvy corners
- > Calculate time between two dates
-
May
-
Other stuff, er...
...I lead front-end development at London-based Espresso Education. More info...
...I'm part of the O'Reilly Blogger Review Scheme