Mengganti fungsi JavaScript saat mereferensikan yang asli

160

Saya memiliki fungsi,, a()yang ingin saya timpa, tetapi juga yang asli a()dilakukan dalam urutan tergantung pada konteksnya. Misalnya, kadang-kadang ketika saya membuat halaman saya ingin menimpa seperti ini:

function a() {
    new_code();
    original_a();
}

dan terkadang seperti ini:

function a() {
    original_a();
    other_new_code();
}

Bagaimana saya mendapatkan itu original_a()dari dalam perjalanan a()? Apakah itu mungkin?

Tolong jangan menyarankan alternatif untuk naik dengan cara ini, saya tahu banyak. Saya bertanya tentang cara ini secara khusus.

Kev
sumber

Jawaban:

179

Anda dapat melakukan sesuatu seperti ini:

var a = (function() {
    var original_a = a;

    if (condition) {
        return function() {
            new_code();
            original_a();
        }
    } else {
        return function() {
            original_a();
            other_new_code();
        }
    }
})();

Mendeklarasikan original_adi dalam fungsi anonim mencegahnya mengacaukan namespace global, tetapi tersedia di fungsi dalam.

Seperti Nerdmaster yang disebutkan dalam komentar, pastikan untuk memasukkan ()di bagian akhir. Anda ingin memanggil fungsi luar dan menyimpan hasilnya (salah satu dari dua fungsi dalam) a, bukan menyimpan fungsi luar itu sendiri a.

Matthew Crumley
sumber
25
Untuk setiap idiot di luar sana seperti saya - perhatikan "()" di akhir - tanpa itu, ia mengembalikan fungsi luar, bukan fungsi dalam :)
Nerdmaster
@ Nerdmaster Terima kasih telah menunjukkannya. Saya menambahkan catatan untuk membantu orang-orang memperhatikan.
Matthew Crumley
5
Wow, saya mencoba melakukan ini tanpa namespace dan saya mendapat stack overflow, lol.
gosukiwi
saya pikir hasilnya akan sama jika kurung pertama dan terakhir dihapus dari fungsi. Seperti dari sini (functi..dan }). Melakukan saja }();akan menghasilkan hasil yang sama, seperti (function{})()set kurung pertama digunakan karena Anda tidak bisa mendeklarasikan, anonim, ekspresi fungsi yang Anda harus tetapkan. Tetapi dengan melakukan () Anda tidak mendefinisikan apa pun hanya mengembalikan fungsi.
Muhammad Umer
@MuhammadUmer Anda benar bahwa tanda kurung di sekitar ekspresi fungsi tidak diperlukan. Saya memasukkan mereka karena tanpa mereka, jika Anda hanya melihat pada baris pasangan pertama, sepertinya (bagi saya setidaknya) seperti Anda menetapkan fungsi luar secara langsung a, tidak memanggilnya dan menyimpan nilai kembali. Tanda kurung memberi tahu saya ada sesuatu yang terjadi.
Matthew Crumley
88

The Pola Proxy mungkin membantu Anda:

(function() {
    // log all calls to setArray
    var proxied = jQuery.fn.setArray;
    jQuery.fn.setArray = function() {
        console.log( this, arguments );
        return proxied.apply( this, arguments );
    };
})();

Di atas membungkus kode dalam suatu fungsi untuk menyembunyikan "proksi" -variable. Ini menyimpan metode setArray jQuery dalam penutupan dan menimpanya. Proxy kemudian mencatat semua panggilan ke metode dan mendelegasikan panggilan ke aslinya. Menggunakan berlaku (ini, argumen) menjamin bahwa penelepon tidak akan dapat melihat perbedaan antara metode asli dan proksi.

PhilHoy
sumber
10
memang. penggunaan 'berlaku' dan 'argumen' membuat ini jauh lebih kuat daripada jawaban lain
Robert Levy
Perhatikan bahwa pola ini tidak memerlukan jQuery — mereka hanya menggunakan salah satu fungsi jQuery sebagai contoh.
Kev
28

Terima kasih kawan, pola proksi sangat membantu ..... Sebenarnya saya ingin memanggil fungsi global foo .. Di halaman tertentu saya perlu melakukan beberapa pengecekan. Jadi saya melakukan yang berikut.

//Saving the original func
var org_foo = window.foo;

//Assigning proxy fucnc
window.foo = function(args){
    //Performing checks
    if(checkCondition(args)){
        //Calling original funcs
        org_foo(args);
    }
};

Terima kasih, ini sangat membantu saya

Naresh S
sumber
3
Saat mengikuti pola proxy , dalam beberapa kasus akan penting untuk menerapkan detail yang tidak ada dalam kode Jawaban ini: Alih-alih org_foo(args), panggil org_foo.call(this, args). Itu tetap thisseperti ketika window.foo panggilan normal (tidak proksi). Lihat Jawaban ini
cellepo
10

Anda dapat mengganti fungsi menggunakan konstruksi seperti:

function override(f, g) {
    return function() {
        return g(f);
    };
}

Sebagai contoh:

 a = override(a, function(original_a) {
      if (condition) { new_code(); original_a(); }
      else { original_a(); other_new_code(); }
 });

Sunting: Memperbaiki kesalahan ketik.

Hai Phan
sumber
+1 Ini jauh lebih mudah dibaca daripada contoh pola proksi lainnya!
Mu Mind
1
... tetapi jika saya tidak salah, ini terbatas pada fungsi argumen-nol, atau setidaknya sejumlah argumen yang telah ditentukan. Apakah ada cara untuk menggeneralisasikannya ke sejumlah argumen yang sewenang-wenang tanpa kehilangan keterbacaan?
Mu Mind
@MuMind: ya, tetapi menggunakan pola proxy dengan benar - lihat jawaban saya .
Dan Dascalescu
4

Melewati argumen sewenang-wenang:

a = override(a, function(original_a) {
    if (condition) { new_code(); original_a.apply(this, arguments) ; }
    else { original_a.apply(this, arguments); other_new_code(); }
});
Edric Garran
sumber
1
tidak akan ada argumen yang merujuk original_a?
Jonathan.
2

Jawaban yang diberikan @Matthew Crumley adalah memanfaatkan ekspresi fungsi yang segera dipanggil, untuk menutup fungsi 'a' yang lebih lama ke dalam konteks eksekusi dari fungsi yang dikembalikan. Saya pikir ini adalah jawaban terbaik, tetapi secara pribadi, saya lebih suka meneruskan fungsi 'a' sebagai argumen ke IIFE. Saya pikir itu lebih bisa dimengerti.

   var a = (function(original_a) {
        if (condition) {
            return function() {
                new_code();
                original_a();
            }
        } else {
            return function() {
                original_a();
                other_new_code();
            }
        }
    })(a);
Rodrigo Hernandez
sumber
1

Contoh di atas tidak berlaku thisatau lulus argumentsdengan benar ke fungsi menimpa. Garis bawah _.wrap () membungkus fungsi yang ada, berlaku thisdan diteruskan argumentsdengan benar. Lihat: http://underscorejs.org/#wrap

nevf
sumber
0

Saya memiliki beberapa kode yang ditulis oleh orang lain dan ingin menambahkan baris ke fungsi yang tidak dapat saya temukan dalam kode. Jadi sebagai solusi saya ingin menimpanya.

Tidak ada solusi yang bekerja untuk saya.

Inilah yang berhasil dalam kasus saya:

if (typeof originalFunction === "undefined") {
    originalFunction = targetFunction;
    targetFunction = function(x, y) {
        //Your code
        originalFunction(a, b);
        //Your Code
    };  
}
Sagar Sodah
sumber
0

Saya telah membuat pembantu kecil untuk skenario yang sama karena saya sering perlu mengganti fungsi dari beberapa perpustakaan. Helper ini menerima "namespace" (wadah fungsi), nama fungsi, dan fungsi utama. Ini akan menggantikan fungsi asli di namespace yang dirujuk dengan yang baru.

Fungsi baru menerima fungsi asli sebagai argumen pertama, dan argumen fungsi asli sebagai sisanya. Ini akan menjaga konteks setiap saat. Ini mendukung fungsi void dan non-void juga.

function overrideFunction(namespace, baseFuncName, func) {
    var originalFn = namespace[baseFuncName];
    namespace[baseFuncName] = function () {
        return func.apply(this, [originalFn.bind(this)].concat(Array.prototype.slice.call(arguments, 0)));
    };
}

Penggunaan misalnya dengan Bootstrap:

overrideFunction($.fn.popover.Constructor.prototype, 'leave', function(baseFn, obj) {
    // ... do stuff before base call
    baseFn(obj);
    // ... do stuff after base call
});

Saya tidak membuat tes kinerja apa pun. Itu mungkin dapat menambahkan beberapa overhead yang tidak diinginkan yang bisa atau tidak bisa menjadi masalah besar, tergantung pada skenario.

Zoltán Tamási
sumber
0

Menurut pendapat saya, jawaban teratas tidak dapat dibaca / dipelihara, dan jawaban lainnya tidak mengikat konteks dengan benar. Berikut adalah solusi yang dapat dibaca menggunakan sintaks ES6 untuk menyelesaikan kedua masalah ini.

const orginial = someObject.foo;
someObject.foo = function() {
  if (condition) orginial.bind(this)(...arguments);
};
James L.
sumber
Tetapi menggunakan sintaks ES6 membuatnya agak terbatas digunakan. Apakah Anda memiliki versi yang berfungsi di ES5?
eitch
0

Jadi jawaban saya akhirnya menjadi solusi yang memungkinkan saya untuk menggunakan variabel _ ini menunjuk ke objek asli. Saya membuat contoh baru dari "Kotak" tetapi saya benci cara "Kotak" menghasilkan ukuran itu. Saya pikir itu harus mengikuti kebutuhan spesifik saya. Namun untuk melakukannya, saya membutuhkan kuadrat untuk memiliki fungsi "GetSize" yang diperbarui dengan internal fungsi yang memanggil fungsi lain yang sudah ada di alun-alun seperti this.height, this.GetVolume (). Tetapi untuk melakukannya saya perlu melakukan ini tanpa ada peretasan yang gila. Jadi, inilah solusi saya.

Beberapa fungsi penginisialisasi atau fungsi pembantu lainnya.

this.viewer = new Autodesk.Viewing.Private.GuiViewer3D(
  this.viewerContainer)
var viewer = this.viewer;
viewer.updateToolbarButtons =  this.updateToolbarButtons(viewer);

Berfungsi di objek lain.

updateToolbarButtons = function(viewer) {
  var _viewer = viewer;
  return function(width, height){ 
blah blah black sheep I can refer to this.anything();
}
};
SteckDEV
sumber
0

Tidak yakin apakah itu akan berfungsi dalam semua keadaan, tetapi dalam kasus kami, kami mencoba untuk menimpa describefungsi di Jest sehingga kami dapat mengurai nama dan melewatkan keseluruhandescribe blok jika memenuhi beberapa kriteria.

Inilah yang berhasil bagi kami:

function describe( name, callback ) {
  if ( name.includes( "skip" ) )
    return this.describe.skip( name, callback );
  else
    return this.describe( name, callback );
}

Dua hal yang sangat penting di sini:

  1. Kami tidak menggunakan fungsi panah () => .

    Fungsi panah mengubah referensi ke thisdan kami membutuhkannya menjadi file this.

  2. Penggunaan this.describedan this.describe.skipbukannya adil describedan describe.skip.

Sekali lagi, tidak yakin itu bernilai bagi siapa pun, tetapi kami awalnya mencoba untuk lolos dengan jawaban Matthew Crumley yang sangat baik, tetapi perlu membuat metode kami berfungsi dan menerima params untuk menguraikannya dalam kondisi.

Joshua Pinter
sumber