Błąd zakresu podczas zastępowania metody Object.prototype konstruktorem Function

Nov 30 2020

Próbuję przesłonić Object.prototype.toStringofertę, aby dodać funkcjonalność dla dodatkowych opisów klas.

Oto kod początkowy:

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

Kiedy uruchamiam to w konsoli, otrzymuję następujący wynik:

[object TestClass]

Dobrą rzeczą jest to, że nie drastycznie zmienić sposób Object.prototype.toStringdziała, więc z innego rodzaju [czyli nie TestClass], wszystko działa tak jak oczekiwanego np Object.prototype.toString.call(12)wyjście będzie [object Number].

Ta implementacja działa jak dotąd bez żadnych problemów. Mam jednak inną implementację z następującym kodem:

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

Dzięki temu otrzymuję odpowiednie dane wyjściowe dla TestClass, ale kiedy używam czegoś innego, na przykład 12, otrzymuję 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)

Wydaje się, że jest to problem z rekurencją toString.apply. Jednak nie mogę dowiedzieć się, dlaczego ta druga implementacja się powtarza, jeśli pierwsza nie?

Uwaga : Powodem tej drugiej implementacji jest if(this instanceof MyClassType){return '[object MyClassType]'}dynamiczne dodanie kodu sprawdzającego typ [tj. ] Dla różnych klas z listy nazw klas w tablicy. Innymi słowy, zamiast modyfikować kod dla każdej nowej klasy, którą wymyślę, zamiast tego dołączam nazwę klasy do tablicy, a instrukcja warunkowa jest generowana automatycznie.

Odpowiedzi

3 Bergi Nov 30 2020 at 15:42

Problem polega na tym, że toStringparametr twojego IIFE nie znajduje się w zakresie w twoim new Functionkodzie. Zamiast tego używa global toString= window.toString= Object.prototype.toString.

Aby to naprawić, musisz zadeklarować toStringzmienną w new Functionkodzie, aby zwrócone zamknięcie działało. Albo jako prosta stała:

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

lub jako parametr:

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