Ошибка диапазона при переопределении метода Object.prototype с помощью конструктора функции
Я пытаюсь переопределить Object.prototype.toStringзаявку на добавление функций для дополнительных описаний классов.
Вот исходный код:
(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);
Когда я запускаю это в консоли, я получаю следующий результат:
[object TestClass]
Хорошо то, что он Object.prototype.toStringне меняет радикально способ работы, поэтому с другим типом [т.е. не TestClass] все работает так, как ожидалось, например, Object.prototype.toString.call(12)будет вывод [object Number].
Эта реализация пока работает без проблем. Однако у меня есть другая реализация со следующим кодом:
(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);
Благодаря этому я получаю правильный результат для TestClass, но когда я использую что-то еще, например 12, я получаю 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)
Похоже, это проблема с рекурсией toString.apply. Однако я не могу понять, почему эта вторая реализация рекурсивна, а первая - нет?
Примечание . Причина этой второй реализации - if(this instanceof MyClassType){return '[object MyClassType]'}динамическое добавление кода проверки типов [т.е. ] для различных классов из списка имен классов в массиве. Другими словами, вместо того, чтобы изменять код для каждого нового класса, который я придумываю, я вместо этого добавляю имя класса в массив, и условный оператор генерируется автоматически.
Ответы
Проблема в том, что toStringпараметр вашего IIFE не входит в область действия вашего new Functionкода. Вместо этого он использует global toString= window.toString= Object.prototype.toString.
Чтобы исправить это, вам нужно объявить toStringпеременную внутри new Functionкода, чтобы возвращаемое замыкание работало. Либо как простая константа:
(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();
})();
или как параметр:
(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);
// ^^^^^^^^^^^^^^^^^^^^^^^^^
})();