I'm trying to override Object.prototype.toString
in a bid to add functionality for additional class descriptions.
Here's the initial code:
(function(toString){
Object.prototype.toString = function(){
if(this instanceof TestClass)
{
return '[object TestClass]';
}
return toString.apply(this, arguments);
}
})(Object.prototype.toString);
function TestClass(){}
var instance_obj = new TestClass();
Object.prototype.toString.call(instance_obj);
When I run this in the console, I get the following output:
[object TestClass]
The good thing is that it doesn't drastically modify the way Object.prototype.toString
works, so with another type [i.e. not TestClass], things work just as expected e.g. Object.prototype.toString.call(12)
will output [object Number]
.
This implementation works with no issues so far. However, I have another implementation with the following code:
(function(toString){
var fn_code_str = `return function(){
if(this instanceof TestClass)
{
return '[object TestClass]';
}
return toString.apply(this, arguments);
}`;
var pre_fn = new Function(fn_code_str);
Object.prototype.toString = pre_fn();
})(Object.prototype.toString);
function TestClass(){}
var instance_obj = new TestClass();
Object.prototype.toString.call(instance_obj);
With this, I get the proper output for TestClass, but when I use something else, like 12
, I get a RangeError:
VM527:5 Uncaught RangeError: Maximum call stack size exceeded
at Function.[Symbol.hasInstance] (<anonymous>)
at Number.eval (eval at <anonymous> (getElements.html:19), <anonymous>:5:21)
at Number.eval (eval at <anonymous> (getElements.html:19), <anonymous>:10:29)
at Number.eval (eval at <anonymous> (getElements.html:19), <anonymous>:10:29)
at Number.eval (eval at <anonymous> (getElements.html:19), <anonymous>:10:29)
at Number.eval (eval at <anonymous> (getElements.html:19), <anonymous>:10:29)
at Number.eval (eval at <anonymous> (getElements.html:19), <anonymous>:10:29)
at Number.eval (eval at <anonymous> (getElements.html:19), <anonymous>:10:29)
at Number.eval (eval at <anonymous> (getElements.html:19), <anonymous>:10:29)
at Number.eval (eval at <anonymous> (getElements.html:19), <anonymous>:10:29)
This appears to be an issue with recursion of toString.apply
. However, I can't figure out why this second implementation is recursing, if the first one does not?
Note: The reason for this second implementation is to add the type-checking code [i.e. if(this instanceof MyClassType){return '[object MyClassType]'}
] for different classes dynamically from a list of class names in an array. In other words, rather than modifying code for each new Class I come up with, I append the class name to the array instead, and the conditional statement is generated automatically.
The problem is that the toString
parameter of your IIFE is not in scope in your new Function
code. Instead, it uses the global toString
= window.toString
= Object.prototype.toString
.
To fix this, you need to declare the toString
variable inside the new Function
's code to make the returned closure work. Either as a simple constant:
(function() {
var pre_fn = new Function(`
const toString = Object.prototype.toString;
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
return function(){
if(this instanceof TestClass) {
return '[object TestClass]';
}
return toString.apply(this, arguments);
}`);
Object.prototype.toString = pre_fn();
})();
or as a parameter:
(function() {
var pre_fn = new Function('toString', `
// ^^^^^^^^^^^
return function(){
if(this instanceof TestClass) {
return '[object TestClass]';
}
return toString.apply(this, arguments);
}`);
Object.prototype.toString = pre_fn(Object.prototype.toString);
// ^^^^^^^^^^^^^^^^^^^^^^^^^
})();