I have recently learned about the magic of fn.apply() in Javascript, and I am using it to save function calls with all their arguments intact and call them at a later date.
However, in my use case, I do not need the first argument, the context (this), and I'd like to avoid passing the this object into it somehow in order to make it clear in my code that I'm not using .apply() for that.
My first thought was passing in null, but I read the following on MDN:
if the method is a function in non-strict mode code, null and undefined will be replaced with the global object, and primitive values will be boxed.
This seems to imply that passing null or even false is bad practice. What would be an appropriately falsy, empty, or otherwise obviously placeholder value to put into fn.apply()'s first argument?
This seems to imply that passing null or even false is bad practice
No, it's not. Admittedly false is a bit weird as a this value, but the problem that MDN warns about are sloppy-mode functions. Examples:
function sloppy() {
console.log(this);
}
function strict() {
"use strict";
console.log(this);
}
sloppy.call(null); // <Window> (or whatever the global object is)
sloppy.call(false); // Boolean {}
strict.call(null); // null
strict.call(false); // false
So just use strict mode and you'll be fine. And don't be surprised that you can't get primitive this values in sloppy mode functions.
Using null or undefined is recommended if you want to pass no value.
Related
Recently I investigated a situation in which a programmer inadvertently passed undefined into addEventListener, thus:
window.addEventListener('load', undefined);
No error was thrown. It's as if JavaScript is willing to invoke undefined. But what in the world is undefined()? I have tried all sorts of things, e.g.:
console.log(undefined() === null);
console.log(typeof undefined());
but I never get anything back.
Edit added for clarity: My original question was based on a mistake, as I had not set my Developer tools to log errors to the console. The above two commands (but not the call to addEventListener) DO throw errors in a browser, as answers and comments below indicate.
It's as if JavaScript is willing to invoke undefined.
No, addEventListener is specified to ignore null, which JavaScript’s undefined is converted to. undefined is never called.
Further proof that JavaScript is not willing to invoke undefined, in a browser:
> undefined()
Uncaught TypeError: undefined is not a function
at <anonymous>:1:1
You can do something like this to find the type of passed arguments:
var myVar;
Object.prototype.toString.call(myVar);
and it will return "[object Undefined]"
same for other use cases like if myVar is a string as below:
var myVar = 'It is a string';
Object.prototype.toString.call(myVar);
it will retrun "[object String]"
It will return:
Undefined is not a function
console.log(typeof undefined());
If you run it console on a browser it will return undefined not a function. Undefined does not have any value unlike null which is an object.
null means that something exists, but has been told it has no value.
undefined means that thing has not been given a value, usually because it hasn't been declared/initialized.
In Javascript, undefined is a primitive. It's falsey, so it evaluates to False if used in conditional.
Javascript is not a strongly typed language, so there's nothing to check that a callback function is a function until it's called. In fact Javascript doesn't care how many arguments are passed or what their type is, everything's just dumped in when a function is invoked, and it's up to the function how to handle the arguments.
For example in many enumerable methods, they pass back to you (index, value, array). It doesn't matter if your function looks for these values or assigns them a temporary variable, they're still passed. Both a.forEach(function(index){}) and a.forEach(function(){}) actually have access to all 3 of the variables mentioned.
I have noticed jQuery and related keynote plugins like jQuery.UI pass undefined as a parameter into anonymous functions used in their module definitions, like so:
(function($, undefined) { ... })(jQuery);
Alternatively, I have noticed other plugins recommended by jQuery and/or others do NOT pass undefined in as a parameter.
This is probably a silly question, but...
Shouldn't it always be available anyway? Why pass it in? Is there some sort of other purpose or trick going on here?
There are two reasons for that:
1) If undefined is a variable in the function scope rather than a global object property, minifiers can reduce it to a single letter thus achieving a better compression rate.
2) Before ES5*, undefined was a property of the global object without write-protection. So it could be overwritten, resulting in strange and unexpected behavior. You could do something like this:
var test = 123;
undefined = 123;
if (test === undefined){
// this will actually be true, since undefined now equals 123
}
By having an function argument undefined (the name actually does not matter) which you don't pass a parameter to, you could make sure you have a variable which really is undefined so you can test "undefinedness" against this variable.
Btw. the safest method to test for undefined is: typeof ( var ) === 'undefined'
(*) With EcmaScript 5, the global properties undefined, NaN and Infinity became readonly. So with its general adoption in all modern browsers - of course with the exception of IE 9 - overwriting those values was not possible anymore.
That is done to make sure that undefined always is undefined. In older versions of the ECMAScript spec (prior to ECMAScript 5), undefined wasn't a reserved word but a regular variable. In older browsers this would be allowed for instance:
undefined = 2; // Assign a different value to undefined
// Now this statement would be true
if (undefined == 2)
So to make sure that undefined is in fact undefined, even if some other "evil" script would have reassigned undefined with another value, you create a parameter that you call undefined, and then when you call the function, you make sure to not pass a value to that parameter - thus you can be sure that the variable undefined will always be undefined within your function.
So in the case of jQuery:
(function($, undefined) { ... })(jQuery);
They pass in jQuery and assign it to the $ variable for convenience, but then they don't pass a second value to the undefined parameter, thus undefined will be undefined.
Modern browsers
In modern browsers, undefined is a non-configurable, non-writable property per the ECMAScript 5 specification.
undefined is not a reserved word in javascript, it is simply a variable. jQuery is ensuring that if some bad developer overwrites the value of undefined in their javascript, then jQuery will ignore it and establish it's own variable, undefined, which is never passed a value (see the self-executing (jQuery) at the end) and is therefore actually undefined.
This doesn't work:
var s = '^foo';
console.log(['boot', 'foot'].some(s.match));
Uncaught TypeError: String.prototype.match called on null or undefined
But this does:
var s = '^foo';
console.log(['boot', 'foot'].some(function(i) { return i.match(s) }));
Why is this? I imagine somehow the String.prototype.match function is too "primitive" or something, but why exactly? Since I'm not using ES2015, the second version seems quite verbose. Is there an alternative?
EDIT
When I wrote the above, I actually got it backwards compared to my actual need, which was matching one string against a number of regexes. But thanks to the great answers and comments below, I get it: [/^foo/, /^boo/].some(''.match, 'boot').
Note: The value of this is determined by how the function is called! (exception: bound and arrow functions)
If you pass s.match to .some, then the function will be called with this set to the global object (e.g. window) not the string it "belongs" to.
I.e. it would be equivalent to this:
String.prototype.match.call(window, 'foo')
This cannot work because this has to refer to a string object.
You could solve this by binding the function to a specific this value:
['boot', 'foot'].some(s.match.bind(s));
Learn more about this:
MDN - this
You Don't Know JS: this or That?
How to access the correct `this` context inside a callback?
A function value in Javascript does not bring its object along with it. The value of s.match is a plain function value, with no knowledge that you happened to find it attached to s. In fact, no matter what String you access it through, it's always the same function value:
"foo".match === "bar".match
//= true
When you call a function through an object, Javascript sets this to that object for the duration of the function call. But as soon as anything comes between retrieving the function value and calling it, any object association is lost.
You can create a function that does remember a specific this value using bind, as in #Felix King's answer. someFunction.bind(someObject) has approximately the same meaning as function(arg1, arg2,...) { return someObject.someFunction(arg1, arg2,...); }, but it automatically handles the number of parameters properly.
I'm going through John Resig's JavaScript ninja tutorial and on #51 I see this:
// Find the largest number in that array of arguments
var largestAllButFirst = Math.max.apply( Math, allButFirst );
allButFirst is just a small array of integers. I believe I understand what apply does, but I can't understand why Math is being passed as an argument to apply.
The first parameter of the .apply is the context. Inside the function body the this keyword will reference that value.
Example:
function sum(a){ return this + a; }
sum.apply(1, [1]); // will return 2
// or with .call
sum.call(1, 1); // also returns 2
By default if you call Math.max the context (the this keyword) is automatically set to Math. To keep this behavior Math is passed as the first parameter in apply.
Passing it Math is not necessary, anything will work here. Math indicates the context of the operation, however max does not require a context. This means that Math.max.apply(undefined, allButFirst) will also work. See this answer.
From Mozilla docs:
fun.apply(thisArg, [argsArray])
thisArg: The value of this provided for the call to fun. Note that this
may not be the actual value seen by the method: if the method is a
function in non-strict mode code, null and undefined will be replaced
with the global object, and primitive values will be boxed.
So, in your example, Math is being used as the context for the function (if the keyword this is used inside).
If no thisArg is used, then the default is the global object. So, it is good practice to give some context if possible.
I have been toying around with obfuscating Javascript with just brackets and other symbols, as per this question and this automatic generator - for purely educational reasons, may I say :)
For example, evaluating (![]+[])[+!+[]] gives me the letter "a".
However, it seems that the examples rely on [].sort.call() returning the window object. My problem is that whenever this doesn't seem to work on any of the browsers I have installed (Chrome 14, FF 9, IE 9):
//They told me this would return the window object
[].sort.call()
//But I get an exception instead:
"TypeError: Array.prototype.sort called on null or undefined"
So I ask:
Was [].sort.call() fixed on recent browsers or does it still return the window object and its just me doing something wrong?
If it is the case that I can't use this approach anymore, are there any other ways I can access the window object using only brackets, parenthesis, exclamation marks and the + operators?
This was changed with ECMAScript 5. From 15.3.4.4:
NOTE The thisArg value is passed without modification as the this value. This is a change from Edition 3, where a undefined or null thisArg is replaced with the global object and ToObject is applied to all other values and that result is passed as the this value.
...and sort() calls ToObject on that this value, throwing the TypeError exception.
And, given the addition of strict mode, which further reduces access to the global object, your options are probably few. Though, without "use strict", you might try using this.