Rentang kesalahan saat mengganti metode Object.prototype dengan fungsi konstruktor

Nov 30 2020

Saya mencoba untuk mengganti Object.prototype.toStringdalam upaya untuk menambahkan fungsionalitas untuk deskripsi kelas tambahan.

Berikut kode awalnya:

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

Ketika saya menjalankan ini di konsol, saya mendapatkan output berikut:

[object TestClass]

Hal yang baik adalah bahwa itu tidak secara drastis mengubah cara Object.prototype.toStringkerjanya, jadi dengan tipe lain [yaitu bukan TestClass], semuanya bekerja seperti yang diharapkan misalnya Object.prototype.toString.call(12)akan menampilkan [object Number].

Implementasi ini berfungsi tanpa masalah sejauh ini. Namun, saya memiliki implementasi lain dengan kode berikut:

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

Dengan ini, saya mendapatkan output yang tepat untuk TestClass, tetapi ketika saya menggunakan sesuatu yang lain, seperti 12, saya mendapatkan 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)

Ini tampaknya menjadi masalah dengan rekursi toString.apply. Namun, saya tidak tahu mengapa penerapan kedua ini berulang, jika yang pertama tidak?

Catatan : Alasan implementasi kedua ini adalah untuk menambahkan kode pemeriksaan tipe [yaitu if(this instanceof MyClassType){return '[object MyClassType]'}] untuk kelas yang berbeda secara dinamis dari daftar nama kelas dalam larik. Dengan kata lain, daripada mengubah kode untuk setiap Kelas baru yang saya buat, saya menambahkan nama kelas ke array, dan pernyataan bersyarat dibuat secara otomatis.

Jawaban

3 Bergi Nov 30 2020 at 15:42

Masalahnya adalah bahwa toStringparameter IIFE Anda tidak berada dalam cakupan new Functionkode Anda . Sebaliknya, ini menggunakan global toString= window.toString= Object.prototype.toString.

Untuk memperbaikinya, Anda perlu mendeklarasikan toStringvariabel di dalam new Functionkode agar penutupan yang dikembalikan berfungsi. Baik sebagai konstanta sederhana:

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

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