Erreur de plage lors de la substitution de la méthode Object.prototype avec le constructeur Function

Nov 30 2020

J'essaie de remplacer Object.prototype.toStringdans une offre pour ajouter des fonctionnalités pour des descriptions de classe supplémentaires.

Voici le code initial:

(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);

Lorsque je l'exécute dans la console, j'obtiens la sortie suivante:

[object TestClass]

La bonne chose est qu'il ne modifie pas radicalement la façon dont les Object.prototype.toStringtravaux, donc avec un autre type [ie non TestClass], les choses fonctionnent comme prévu par exemple la Object.prototype.toString.call(12)production de volonté [object Number].

Cette implémentation fonctionne sans aucun problème jusqu'à présent. Cependant, j'ai une autre implémentation avec le code suivant:

(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);

Avec cela, j'obtiens la sortie appropriée pour TestClass, mais lorsque j'utilise autre chose, comme 12, j'obtiens un 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)

Cela semble être un problème avec la récursivité de toString.apply. Cependant, je ne peux pas comprendre pourquoi cette deuxième implémentation est récurrente, si la première ne le fait pas?

Remarque : La raison de cette seconde implémentation est d'ajouter if(this instanceof MyClassType){return '[object MyClassType]'}dynamiquement le code de vérification de type pour différentes classes à partir d'une liste de noms de classe dans un tableau. En d'autres termes, plutôt que de modifier le code pour chaque nouvelle classe que je propose, j'ajoute le nom de la classe au tableau à la place, et l'instruction conditionnelle est générée automatiquement.

Réponses

3 Bergi Nov 30 2020 at 15:42

Le problème est que le toStringparamètre de votre IIFE n'est pas dans la portée de votre new Functioncode. Au lieu de cela, il utilise le global toString= window.toString= Object.prototype.toString.

Pour résoudre ce problème, vous devez déclarer la toStringvariable dans le new Functioncode de pour que la fermeture retournée fonctionne. Soit comme une simple constante:

(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();
})();

ou en paramètre:

(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);
//                                     ^^^^^^^^^^^^^^^^^^^^^^^^^
})();