Why doesn't JavaScript function aliasing work? - javascript

I have some Firebug console function calls that I wanted to disable when Firebug wasn't enabled, e.g. console isn't defined. This works fine in IE6 and FF3, but not in Chrome:
var log;
if(console){
log = console.log;
}else{
log = function(){ return; }
}
I get an "Uncaught TypeError: Illegal Invocation" in Chrome =/
I read about the issue here, where you have to apply a context, which is kind of new to me... and I can't seem to figure how to accomplish the above in all browsers...

Yes, you should persist the context :
var log;
if (window.console && typeof console.log === "function"){
// use apply to preserve context and invocations with multiple arguments
log = function () { console.log.apply(console, arguments); };
} else {
log = function(){ return; }
}
What is happening is that the context (the this value), is implicitly set when you call a function, for example:
var obj = {
method: function () { return this; }
};
obj.method() === obj; // true
In this case, you are calling a function that is defined as a property of an object, when the function is invoked, the this value is set to that object.
Now as in your example, if you copy a reference of that method to a variable:
var method = obj.method;
method() === window; // global object
As you can see, the this value refers to the global object.
So, to avoid this implicit behavior you can set the context explicitly, with the call or apply functions.

The problem with wrapping a function (like console.log) in a function is that it loses its context, i.e. it will not show the correct line number of the file that we've put our "log" shortcut in.
Instead I suggest something like this:
window.log = ((window.console && window.console.log) ?
console.log.bind(console) :
function(){});
This works with firebug & chrome dev tools and does not throw errors when no console is available. And - most importantly - shows the correct file & line number.

This doesn't work:
log("hi");
While this does:
log.call(console, "hi");
It is obvious that you need to call the aliased function with the correct context -- as you yourself have mentioned.
I think you'll have to use a function wrapper (a closure that has a reference to the original context) rather than an alias...
Update
Also note that if you check for console directly, you may get a run-time error when the variable doesn't exist. You're better off checking it explicitly as window.console. Here's one way to implement a conditional log wrapper:
var log = (function (console) {
return console
? function () { console.log.apply(console, arguments); }
: function () {}
})(window.console);

This solution modifies the earlier and excellent answer from CMS to work with IE8. You’ll need to open the IE8 console (press F12) before executing this. (If you forget, you’ll need to exit IE8 entirely and start again because even if the console exists, IE8 won’t subsequently create the console object.)
Note that we don’t set the context, which was the original problem but, as it turns out, IE8 doesn’t require that context. (Good thing, because IE8 also doesn’t provide the apply method on the console.log object!).
This code works with the latest versions of Chrome, FireFox, and MSIE. (It is compatible with MSIE6 and doesn’t throw an error.)
if((typeof console !== "undefined") && ((typeof console.log) !== "undefined"))
{
if ((typeof console.log.apply !== "undefined"))
{
log = function() { console.log.apply(console,arguments) };
}
else
{
log = console.log;
}
}
else
{
log = function() {};
// alert("No debug console");
}

I did this
var log;
log = function() {
if ((window.console != null) && (window.console.log.apply != null)) {
return console.log.apply(console, arguments);
} else {
return function() {};
}
};

Related

How to deal with `console.log.bind(console)` throwing error in PhantomJS?

Using PhantomJS 1.9.x, I wanted to use console.log.bind(console) in my code, but it throws an error due to console.log.bind being undefined (same for console.error.bind(console) etc.)
It is a known issue with PhantomJS 1.x that it does not support Function.prototype.bind.
However even after including a bind polyfill things are not working as expected:
console.log(Function.prototype.bind);
// function bind(obj) { ... }
console.log(console.log.bind);
// undefined
How can I fix that issue?
It seems that in PhantomJS, console is a bit special in that it is not an instance of Function (contrary to Chrome or Firefox). Hence extending Function.prototype has no action on it.
console.log(typeof console.log === "function");
// true
console.log(console.log instanceof Function);
// false
(probably the console.log comes from a different JavaScript context, and the issue here is the same as with myArray instanceof Array evaluating to false when myArray comes from an iframe).
To fix the issue, apart from including a polyfill for Function.prototype.bind, you could assign bind to console methods manually, like this:
if (!console.log.bind) {
// PhantomJS quirk
console.log.constructor.prototype.bind = Function.prototype.bind;
}
After this, all console methods will have .bind():
console.log(console.log.bind); // function bind(obj) { ... }
console.log(console.info.bind); // function bind(obj) { ... }
console.log(console.debug.bind); // function bind(obj) { ... }
console.log(console.warn.bind); // function bind(obj) { ... }
console.log(console.error.bind); // function bind(obj) { ... }

How can I avoid to write to the console?

I have to avoid to write console.log (also dir etc) in my server prod.
I've tried with
console = {};
But it doesn't work.
Try this bit of code:
var console = {
log : function (string) {
//does nothing
}
}
If often insert this piece of code to allow me to control console logging on client or server:
// must be in the global scope
if (typeof console === "undefined") {
var console = {};
}
if (!console.log) {
console.log = function() {
// put whatever you want here or nothing if you want to stub it out
}
}
Then, any console.log() statements will simply do nothing.
Doing it this way, allows code to actually use the real console.log() if it is defined, but prevents any errors if it is not defined.

What is the current best way to wrap console.log() that will preserve line numbers?

I've previously used the following based on other SO answers (without really understanding the need for (nor the workings of) the prototype.apply.apply
var mylogger = {
log: function () {
if (window.console) {
if (window.console.log) {
Function.prototype.apply.apply(console.log, [console, arguments]);
}
}
},
...
};
while this prevents IE from crapping on itself, it also make the line number reporting unusable (it always reports the apply.apply.. line.
I was playing around a little and discovered that the following seems to do exactly what I need, i.e. prevent IE from crapping on itself and report the line number where mylogger.log(..) was called from..
var mylogger = {
// function invocation returning a safe logging function..
log: (function () {
if (window.console && window.console.log && Function.prototype.bind) {
return window.console.log.bind(window.console);
} else {
return function () {};
}
}()),
...
};
I've done some basic testing on IE/FF/Chrome without seeing any issues.. is this reasonable or is there a better way?
What you're doing is fine I guess, but if you aren't adding any additional functionality, you could do something simple and in one line:
window.console = (window.console || {debug:function(){},log:function(){},error:function(){}});
You could, of course, add other console functions if you use them.

Dynamically loading js script from bookmarklet

I'm trying to load a script so I can use scripts on the page that is spawned by the bookmarklet. (view src: XHR followed by beautify.js followed by prettify.js)
I know what I am basically supposed to do (like this) but what's happening is I can't find a good way to detect when the functions I need are actually loaded.
var doWhenLoaded = function (name) {
if (typeof(eval(name)) === 'function') {
eval(name+'()');
} else {
setTimeout(
function () {
console.log("from timeout: "+new Date().getTime());
doWhenLoaded(name,call);
} , 50
);
}
}
I tried that but eval(name+'()'); throws an error.
I can't answer your question, but to test if a function is available use:
var doWhenLoaded = function (name) {
if (typeof window[name] == 'function') {
window[name]();
} else {
// set the timeout. Should have a limit, else it wil go on forever.
}
...
};
Edit
Updated to use window[name], but really should use a reference to the global object. But I guess it's ok to use window for a browser specific script.
The code above should not throw any errors. Since name is in the formal parameters, it's essentially a declared local variable. If name is undefined, then typeof name will return the string "undefined", which fails the test so name() is not evaluated.
I think I can force the scripts to get loaded synchronously before I end up calling them by simply writing the document rather than setting them into the dom.

Preventing console errors

Whats the best approach for preventing errors when console.log calls have been left in JavaScript and it is executed on Browsers without a console or with console deactivated. Is there a way it can be automatically overridden to become a javascript alert for example?
if(!window.console) console = {log: function(s) {alert(s);}};
You can of course add more of the functions that console normally has.
You have to check if the console identifier is available, you can do it either by using the typeof operator, or by checking window.console, because if you access directly an identifier and it's not defined, you will get a ReferenceError.
For example:
if (typeof console == "undefined") {
window.console = {
log: function () {
// do nothing
}
};
console.warn = console.debug = console.log;
}
Here's what I use :-
if(typeof(console) != "undefined")

Categories