JavaScript Function Context Incorrect - javascript

I noticed a weird thing in javascript. Consider the below:
var fn = ''.toUpperCase.call
console.log(typeof fn); // "function"
fn(); // Uncaught TypeError: `fn` is not a function
The above was executed on my Chrome's Developer Console. Version is 43.0.2357.81 m.
The typeof operator clearly shows that fn is a function, but the error suggests otherwise.
I've noticed that Function.apply shows at least some meaningful error message.
So, when is a function, not a function?

Context in Javascript is always established by the way you call a function.
var fn = ''.toUpperCase.call
This assigns the prototype implementation of the call function to fn. If you now call fn(), there's no context to the call. call would try to invoke the function object it was associated with. However, that context is established at call time. Since you're not giving it any context at call time, some internal component of call is throwing an error.
You'd have to do this:
fn.call(''.toUpperCase)
That's right, you call the call function, establishing a context, namely the toUpperCase string function. In this specific case this would lead to another error inside toUpperCase, since it is not bound to a specific context. You'd need to establish that context explicitly as well:
var fn = ''.toUpperCase.call
fn.call(''.toUpperCase.bind(''))
Also see How does the "this" keyword work?

deceze's answer is correct, I just want to explain it from a different point of view.
Your fn is a reference to Function.prototype.call which needs to be called with a function as its this reference, in this case, the context for call is String.prototype.toUpperCase, which was inherited through ''.toUpperCase
On top of that, String.prototype.toUpperCase also has to be called with a specific context, the string to upper case.
Here's another way to code what you wanted that may help you understand what is going on.
var str = 'aaa';
var upper = ''.toUpperCase;
var fn = upper.call;
// Now we have to specify the context for both upper and fn
console.log( fn.call(function() { return upper.call(str)}) ); // AAA
In your example, fn() is attempting to call call but it's not specifying the context, which typically defaults to the window object but in this case it just makes it undefined, which triggers a weird error in Chrome (as you discovered), but Firefox is clearer about the problem,
TypeError: Function.prototype.call called on incompatible undefined

So, when is a function, not a function?
The function is a function, but you are not executing it correctly, as explained in comments and the answer by #deceze
I noticed a weird thing in javascript.
You seem confused by the message that you are getting in the environment in which you tested, the message is not strictly correct.
Uncaught TypeError: fn is not a function
The current stable version of chromium (40.0.2214.91 on my linux install, chrome fails to run on my hardware without making settings changes), chrome being the environment on which you tested, gives a seemingly more correct error message that makes more sense.
Uncaught TypeError: undefined is not a function
So, were you asking/wanting to ask a serious question or were you just poking a little fun at a mistake in a version of chrome?

Related

Is a function fn passed to setTimeout invoked not as a function but as a method like window.fn()?

In strict mode, this should be undefined inside of a non-method (a function):
see http://jsfiddle.net/jfp06nc9/1/
showing this is undefined
However, when the setTimeout is used, then this is bound to window:
see http://jsfiddle.net/jfp06nc9/2/
and http://jsfiddle.net/jfp06nc9/3/
showing that this === window returns true.
so it looks like the function fn passed to setTimeout is invoked not as a function, but as a method, like window.fn() or fn.call(window) or (fn.bind(window))().
see http://jsfiddle.net/jfp06nc9/4/
showing the last 3 calls above would all show this === window as true.
Is that true? I can't find any specification about it so please include that in your answer.
(the jsfiddle was running in Chrome 46.0)
P.S. This question is NOT about what this is bound to, but about, in non-strict mode, setTimeout can run fn() and the this will be bound to window. But in strict mode, if setTimeout really runs it as fn(), then this should now be undefined instead of window, so there is a subtle difference there, that it seems setTimeout runs it (or arrange to run it) not as fn() but as fn.call(window)
The jsfiddle and code:
http://jsfiddle.net/jfp06nc9/7/
(function() {
"use strict";
setTimeout(function() {
"use strict";
console.log(this);
}, 1000);
}());
Since the function runs in strict mode, if it is run as a function, then this should be undefined but it is in fact window
setTimeout is part of the HTML spec (as opposed to ECMAScript), and it does indeed clarify that window should be used for this.
The relevant part of the spec is as follows:
Let method context proxy be method context if that is a WorkerGlobalScope object, or else the WindowProxy that corresponds to method context.
(emphasis mine)
along with:
4.2 Run the appropriate set of steps from the following list:
If the first method argument is a Function
Call the Function. Use the third and subsequent method arguments (if any) as the arguments for invoking the Function. Use method context proxy as the thisArg for invoking the Function.
(emphasis mine)

Reference to object not same as the object?

Why this works:
var i = document.createElement("input");
document.body.appendChid(i);
But not this:
var i = document.createElement("input");
var f = document.body.appendChild;
console.log(f === document.body.appendChild); //outputs true
f(i);
And the error details is:
TypeError: 'appendChild' called on an object that does not implement interface Node.
In JavaScript, what looks like a "method" doesn't actually "know" what object it's attached to; essentially, it's just a function which happens to have been saved as a property of some object. The determination of what object this will represent happens when you call it, in most circumstances based on the object to the left of the . in the call.
So your variable f points at the right function, but when it's called, it will see the wrong value of this. Since in this case it expects to be called on a DOM Node (in the working test, document.body), calling it outside of that scope raises the error shown.
Note that the above is all slightly simplified, with just enough detail to explain your example. You can probably find further reading by searching for explanations of this as well as call and apply, which are ways of explicitly setting the this binding of a function call.

Function.prototype.apply doesn't work on Function.prototype.call in Chrome

The title should be self explanatory, and I wonder if there is a workaround for this.
Window.call.apply;
>> function apply()
Window.call.apply();
>> Uncaught TypeError: Window.call.apply is not a function
at <anonymous>:2:13
at Object.InjectedScript._evaluateOn (<anonymous>:895:140)
at Object.InjectedScript._evaluateAndWrap (<anonymous>:828:34)
at Object.InjectedScript.evaluate (<anonymous>:694:21)
Basically, I am surprised that apply works on every function except one.
I am writing some higher level VM that needs to call native javascript functions (sadly, Function.prototype.call is one of them), so I need the function apply to be able to pass my this context and array of arguments.
Updated:
It seems that this is just some weird error message.
I found this when I wanted to be able to call a constructor with variable number of arguments and passing this explicitly, which is an object instead of the expected function.
Perhaps below is a more realistic example:
function Fruit(name) {
this.name = name;
}
var x = {};
Fruit.call(x, "Apple");
// I thought this would also work, but it throws above error message
(Fruit.call).apply(x, ["Apple"]);
// Should instead use the constructor directly
Fruit.apply(x, ["Apple"]);
// or
(Fruit.call).apply(someFunction, [x, "Apple"]);
You are getting the error because you are not passing an argument to .apply. Firefox throws a more reasonable error:
TypeError: Function.prototype.call called on incompatible undefined
It works fine in both browsers if you actually pass a function that should be called:
Window.call.apply(function foo() { console.log('foo'); });
Note: If you actually want to apply the Window function to an arbitrary object, this probably won't work. Host objects/functions are often more restrictive, especially when it involves the DOM.

JavaScript PubSub Pattern: What is happening with the context of "this", why is it lost?

I am starting to get my feet wet using the publish/subscribe pattern in JavaScript, but now facing a problem I don't understand.
Take the following scenario:
On button click, a message is emitted, this works.
document.getElementById('myBtn').addEventListener('click', function() {
PubSub.publish('topic', data);
});
In an other part of my application, I pick up this message:
function MyObj() {
this.eventLog = [];
var token = PubSub.subscribe('topic', this.logger);
};
MyObj.prototype.logger = function(message, data) {
this.eventLog.push(data);
}
Here I want to store the published data in the eventLog property of the MyObj object. Unfortunately, this isn't working:
Uncaught TypeError: Cannot read property 'eventLog' of undefined
So it seems like the context of this is lost – when I do console.log(this), the window object is logged.
I know this can be tricky for beginners, but until now I was always able to understand what is happening, but this leaves me completely puzzled. Of course MyObj gets initialized before the message is published, so I don't see any problem here. Could somebody explain to me please what is happening here?
Use Function.prototype.bind:
PubSub.subscribe('topic', this.logger.bind(this))
To clarify/explain the observed behaviour - JavaScript uses lexical scoping, so the this keyword is bound to the most inner scope of the usage. Depending where (in which function and parent of the function and/or object) the this keyword is used, it is a different object.
In your case when calling the this.eventLog in your MyObj this is referring to the MyObj. But in the part
MyObj.prototype.logger = function(message, data) {
this.eventLog.push(data);
}
the this keyword is called in the scope of the window object (parent is the window object) and that is why you see it when printed instead of MyObj.
Technically the context of this is always pushed/saved in a stack, so the context is not lost, but the current context is popped/retrieved from the stack regarding the current scope.
This behaviour is very well (also visually) described in this article Quirksmode - The this keyword.
A few good examples can also be found in this SO post.

How can function references be executed properly (1)?

win points to window. NS is a temporary namespace for this post. I thought that if I wanted access to setTimeout, I could just copy over the function reference as such:
NS.setTimeout = win.setTimeout;
However, execution will throw an error:
NS_ERROR_XPC_BAD_OP_ON_WN_PROTO: Illegal operation on WrappedNative prototype object # ...
To fix this error, I just did:
NS.setTimeout = function (arg1, arg2) {
return win.setTimeout(arg1, arg2);
};
However, I don't know why this fixed it. I don't know what language mechanics are causing this behavior.
What you want is this :
NS.setTimeout = win.setTimeout.bind(win);
or what you already do, if you want to be compatible with IE8.
Because setTimeout, like many window functions, needs the receiver (this) to be window.
Another IE8 compatible solution, more elegant in my opinion than yours (because it doesn't use the fact you know the number of arguments needed by setTimeout), would be
NS.setTimeout = function(){
return win.setTimeout.apply(win, arguments);
};
The reason why you can't do that is because, when assigining, you're changeing the call context of setTimeout, which isn't allowed.
Nor is it allowed for setInterval, and many of the other native objects/functions. Again: a great rule of thumb: if you don't own the object, don't touch it. Since functions are objects in JS, that rule applies to them, too
check the specs on the global object and its properties/builtin funcitons:
There are certain built-in objects available whenever an ECMAScript program begins execution. One, the global object, is part of the lexical environment of the executing program. Others are accessible as initial properties of the global object.
And so on. But the lexical environment is quite significant. By assigning a reference to the function elsewhere, you could well be masking part of the lexical environment, or exposing too much of the global environment (eg mashups).
This fixed the problem b.c. you are changing the calling object back to the original when you call it.
return win.setTimeout(arg1, arg2);
will set the context ( or this ) back to window where it should be. The other answers are similar in the fact that they change the context to the correct value using bind to apply.

Categories

Resources