関数コンストラクターでObject.prototypeメソッドをオーバーライドするときの範囲エラー

Nov 30 2020

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の適切な出力が得られますが、のような他のものを使用すると、12RangeErrorが発生します。

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。ただし、最初の実装が繰り返されないのに、なぜこの2番目の実装が繰り返されるのか理解できません。

:この2番目の実装の理由if(this instanceof MyClassType){return '[object MyClassType]'}は、配列内のクラス名のリストから、さまざまなクラスの型チェックコード[つまり]を動的に追加するためです。つまり、新しいクラスごとにコードを変更するのではなく、代わりにクラス名を配列に追加すると、条件ステートメントが自動的に生成されます。

回答

3 Bergi Nov 30 2020 at 15:42

問題はtoString、IIFEのパラメーターがnew Functionコードのスコープ内にないことです。代わりに、グローバル使用しています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);
//                                     ^^^^^^^^^^^^^^^^^^^^^^^^^
})();