การแทนที่ฟังก์ชั่น JavaScript ในขณะที่อ้างอิงถึงต้นฉบับ


160

ฉันมีฟังก์ชั่นa()ที่ฉันต้องการแทนที่ แต่ก็มีการa()ดำเนินการต้นฉบับในลำดับขึ้นอยู่กับบริบท ตัวอย่างเช่นบางครั้งเมื่อฉันสร้างหน้าฉันจะต้องการแทนที่เช่นนี้:

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

และบางครั้งเช่นนี้

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

ฉันจะได้รับสิ่งนั้นoriginal_a()จากภายในรถได้a()อย่างไร? เป็นไปได้ไหม

โปรดอย่าแนะนำทางเลือกอื่นสำหรับการขี่มากกว่าด้วยวิธีนี้ฉันรู้จากหลาย ๆ ฉันถามเกี่ยวกับวิธีนี้โดยเฉพาะ

คำตอบ:


179

คุณสามารถทำสิ่งนี้:

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

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

การประกาศoriginal_aภายในฟังก์ชั่นที่ไม่ระบุชื่อช่วยป้องกันไม่ให้เกิดความยุ่งเหยิงในเนมสเปซส่วนกลาง แต่ใช้ได้ในฟังก์ชั่นด้านใน

เช่นเดียวกับ Nerdmaster ที่กล่าวถึงในความคิดเห็นอย่าลืมใส่()ตอนท้าย คุณต้องการเรียกใช้ฟังก์ชั่นด้านนอกและเก็บผลลัพธ์ (หนึ่งในสองฟังก์ชั่นด้านใน) ลงไปaแต่ไม่เก็บฟังก์ชั่นด้านนอกaเอาไว้


25
สำหรับโง่ ๆ ที่ออกมีเหมือนตัวเอง - ความสนใจใกล้เคียงกับค่าใช้จ่าย "()" ในตอนท้าย - ไม่ว่าจะส่งกลับฟังก์ชั่นด้านนอกไม่ได้เป็นฟังก์ชั่นภายใน :)
Nerdmaster

@ เนอร์มาสเตอร์ขอขอบคุณที่ชี้ให้เห็นว่า ฉันเพิ่มบันทึกย่อเพื่อหวังช่วยให้ผู้คนสังเกตเห็น
Matthew Crumley

5
ว้าวฉันพยายามทำสิ่งนี้โดยไม่ใช้เนมสเปซ
gosukiwi

ฉันคิดว่าผลลัพธ์จะเหมือนกันถ้าลบวงเล็บแรกและสุดท้ายออกจากฟังก์ชัน ชอบจากที่นี่และ(functi.. })การทำเพียงอย่างเดียว}();จะให้ผลลัพธ์เหมือนกัน(function{})()เนื่องจากมีการใช้วงเล็บในชุดที่ 1 เพราะคุณไม่สามารถประกาศนิพจน์ฟังก์ชั่นที่ไม่ระบุชื่อที่คุณต้องกำหนดได้ แต่ด้วยการทำ () คุณไม่ได้กำหนดอะไรเลยเพียงแค่คืนค่าฟังก์ชัน
มูฮัมหมัดอูเมอร์

@MuhammadUmer คุณพูดถูกว่าวงเล็บไม่จำเป็นต้องใช้วงเล็บ ฉันรวมพวกเขาเพราะไม่มีพวกเขาถ้าคุณแค่ดูสองบรรทัดแรกมันดูเหมือน (อย่างน้อยฉัน) เหมือนคุณกำลังกำหนดฟังก์ชั่นด้านนอกโดยตรงกับaไม่เรียกมันและเก็บค่าส่งคืน วงเล็บให้ฉันรู้ว่ามีสิ่งอื่นเกิดขึ้น
Matthew Crumley

88

รูปแบบหนังสือมอบฉันทะอาจช่วยให้คุณ:

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

ด้านบนล้อมรอบโค้ดในฟังก์ชันเพื่อซ่อน "proxied" -variable มันจะบันทึกวิธี setArray ของ jQuery ในการปิดและเขียนทับมัน จากนั้นพร็อกซีจะบันทึกการโทรทั้งหมดไปยังวิธีการและมอบหมายการโทรให้กับต้นฉบับ การใช้นำไปใช้ (สิ่งนี้ขัดแย้ง) รับประกันได้ว่าผู้โทรจะไม่สามารถสังเกตเห็นความแตกต่างระหว่างวิธีการดั้งเดิมและพร็อกซี


10
จริง การใช้ 'ใช้' และ 'การขัดแย้ง' ทำให้สิ่งนี้แข็งแกร่งกว่าคำตอบอื่น ๆ
Robert Levy

โปรดทราบว่ารูปแบบนี้ไม่จำเป็นต้องใช้ jQuery เพราะเป็นเพียงการใช้หนึ่งในฟังก์ชั่นของ jQuery
Kev

28

ขอบคุณพวกรูปแบบพร็อกซี่ช่วยจริงๆ ..... จริงๆแล้วฉันอยากจะเรียกฟังก์ชั่นสากล foo .. ในบางหน้าฉันต้องทำเพื่อตรวจสอบบางอย่าง ดังนั้นฉันทำต่อไปนี้

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

ขอบคุณสิ่งนี้ช่วยฉันจริงๆ


3
เมื่อต่อไปนี้รูปแบบการพร็อกซี่ก็จะมีความสำคัญในบางกรณีการดำเนินการในรายละเอียดที่ไม่อยู่ในรหัสของคำตอบนี้แทนการโทรorg_foo(args) org_foo.call(this, args)ที่รักษาthisตามที่มันจะได้รับเมื่อ window.foo โทรตามปกติ (ไม่พร็อกซี) ดูคำตอบนี้
cellepo

10

คุณสามารถแทนที่ฟังก์ชั่นโดยใช้โครงสร้างเช่น:

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

ตัวอย่างเช่น:

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

แก้ไข: แก้ไขข้อผิดพลาด


+1 นี่อ่านได้มากกว่าตัวอย่างรูปแบบพร็อกซีอื่น ๆ !
หมู่ใจ

1
... แต่ถ้าฉันไม่เข้าใจผิดนี่ถูก จำกัด ฟังก์ชั่น zero-อาร์กิวเมนต์หรือจำนวนอาร์กิวเมนต์ที่กำหนดไว้ล่วงหน้าอย่างน้อย มีวิธีการทั่วไปเพื่อจำนวนข้อโต้แย้งโดยไม่สูญเสียการอ่าน?
หมู่ใจ

@MuMind: ใช่ แต่ใช้รูปแบบการพร็อกซี่อย่างถูกต้อง - ดูคำตอบของฉัน
Dan Dascalescu

4

ผ่านข้อโต้แย้งโดยพลการ:

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

1
ข้อโต้แย้งจะไม่อ้างถึงoriginal_aแม้ว่า?
โจนาธาน

2

คำตอบที่ @Matthew Crumley ให้ไว้คือการใช้ประโยชน์จากการแสดงออกของฟังก์ชั่นที่เรียกใช้ทันทีเพื่อปิดฟังก์ชั่น 'a' ที่เก่ากว่าลงในบริบทการดำเนินการของฟังก์ชันที่ส่งคืน ฉันคิดว่านี่เป็นคำตอบที่ดีที่สุด แต่โดยส่วนตัวแล้วฉันชอบที่จะใช้ฟังก์ชั่น 'a' เพื่อโต้แย้งกับ IIFE ฉันคิดว่ามันเป็นที่เข้าใจมากขึ้น

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

1

ตัวอย่างด้านบนใช้ไม่ถูกต้องthisหรือส่งผ่านargumentsอย่างถูกต้องกับการแทนที่ฟังก์ชัน ขีดล่าง _.wrap () ล้อมฟังก์ชันที่มีอยู่ใช้thisและผ่านargumentsอย่างถูกต้อง ดู: http://underscorejs.org/#wrap


0

ฉันมีรหัสบางอย่างที่เขียนโดยคนอื่นและต้องการเพิ่มบรรทัดในฟังก์ชันที่ฉันไม่สามารถหาได้ในรหัส ดังนั้นเพื่อเป็นวิธีแก้ปัญหาฉันต้องการแทนที่มัน

ไม่มีวิธีแก้ปัญหาใดที่เหมาะกับฉัน

นี่คือสิ่งที่ทำงานในกรณีของฉัน:

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

0

ฉันได้สร้างผู้ช่วยตัวเล็กสำหรับสถานการณ์ที่คล้ายกันเพราะฉันมักจะต้องการแทนที่ฟังก์ชั่นจากห้องสมุดหลายแห่ง ผู้ช่วยนี้ยอมรับ "namespace" (คอนเทนเนอร์ฟังก์ชัน) ชื่อฟังก์ชันและฟังก์ชันแทนที่ มันจะแทนที่ฟังก์ชั่นเดิมใน namespace ที่อ้างถึงด้วยใหม่

ฟังก์ชั่นใหม่ยอมรับฟังก์ชั่นเดิมเป็นอาร์กิวเมนต์แรกและฟังก์ชั่นเดิมข้อโต้แย้งเป็นส่วนที่เหลือ มันจะรักษาบริบททุกครั้ง สนับสนุนฟังก์ชั่นโมฆะและไม่เป็นโมฆะเช่นกัน

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

ตัวอย่างการใช้ Bootstrap:

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

ฉันไม่ได้สร้างการทดสอบประสิทธิภาพใด ๆ มันอาจจะเพิ่มค่าใช้จ่ายที่ไม่พึงประสงค์ซึ่งอาจหรือไม่สามารถเป็นเรื่องใหญ่ขึ้นอยู่กับสถานการณ์


0

ในความเห็นของฉันคำตอบยอดนิยมไม่สามารถอ่าน / บำรุงรักษาได้และคำตอบอื่น ๆ ไม่ได้ผูกบริบทอย่างเหมาะสม นี่เป็นวิธีแก้ปัญหาที่อ่านได้โดยใช้ไวยากรณ์ ES6 เพื่อแก้ปัญหาทั้งสองนี้

const orginial = someObject.foo;
someObject.foo = function() {
  if (condition) orginial.bind(this)(...arguments);
};

แต่การใช้ไวยากรณ์ ES6 ทำให้ค่อนข้าง จำกัด ในการใช้งาน คุณมีเวอร์ชั่นที่ใช้งานได้กับ ES5 หรือไม่?
eitch

0

ดังนั้นคำตอบของฉันคือการแก้ปัญหาที่ทำให้ฉันสามารถใช้ตัวแปร _this ที่ชี้ไปยังวัตถุดั้งเดิมได้ ฉันสร้างอินสแตนซ์ใหม่ของ "Square" แต่ฉันเกลียดวิธีที่ "Square" สร้างขนาด ฉันคิดว่าควรทำตามความต้องการเฉพาะของฉัน อย่างไรก็ตามเพื่อที่จะทำเช่นนั้นฉันต้องการให้สแควร์มีฟังก์ชั่น "GetSize" ที่อัปเดตพร้อมกับ internals ของฟังก์ชั่นที่เรียกฟังก์ชั่นอื่น ๆ ที่มีอยู่แล้วในสแควร์เช่น this.height this.GetVolume () แต่เพื่อที่จะทำเช่นนั้นฉันจำเป็นต้องทำเช่นนี้โดยไม่ต้องแฮ็คบ้า นี่คือทางออกของฉัน

Object initializer หรือฟังก์ชันตัวช่วยอื่น ๆ

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

ฟังก์ชั่นในวัตถุอื่น ๆ

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

0

ไม่แน่ใจว่าจะใช้งานได้ในทุกสถานการณ์หรือไม่ แต่ในกรณีของเราเราพยายามแทนที่describeฟังก์ชันใน Jest เพื่อให้เราสามารถแยกชื่อและข้ามdescribeบล็อกทั้งหมดหากตรงตามเกณฑ์

นี่คือสิ่งที่ใช้ได้กับเรา:

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

สองสิ่งที่สำคัญที่นี่:

  1. () =>เราไม่ได้ใช้ฟังก์ชั่นลูกศร

    ลูกศรฟังก์ชั่นการเปลี่ยนแปลงการอ้างอิงถึงและเราจำเป็นที่จะต้องมีของไฟล์thisthis

  2. การใช้this.describeและการthis.describe.skipแทนเพียงและdescribedescribe.skip

อีกครั้งไม่แน่ใจว่าเป็นของมีค่าสำหรับใคร แต่แรกเริ่มเราพยายามที่จะหนีไปด้วยคำตอบที่ยอดเยี่ยมของ Matthew Crumley แต่จำเป็นต้องทำให้วิธีการของเราเป็นหน้าที่และยอมรับ params เพื่อแยกวิเคราะห์พวกเขาในเงื่อนไข

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.