Javascript: ขยายฟังก์ชัน


98

เหตุผลหลักที่ฉันต้องการคือฉันต้องการขยายฟังก์ชันเริ่มต้นของฉัน

สิ่งนี้:

// main.js

window.onload = init();
function init(){
     doSomething();
}

// extend.js

function extends init(){
    doSomethingHereToo();
}

ดังนั้นฉันต้องการขยายฟังก์ชันเช่นฉันขยายคลาสใน PHP

และฉันต้องการขยายจากไฟล์อื่นด้วยเช่นฉันมีฟังก์ชัน init ดั้งเดิมในmain.jsและฟังก์ชันขยายในextended.js.



คำตอบ:


106

ด้วยมุมมองที่กว้างขึ้นเกี่ยวกับสิ่งที่คุณกำลังพยายามทำจริงและบริบทที่คุณกำลังทำฉันมั่นใจว่าเราสามารถให้คำตอบที่ดีกว่าคำตอบที่แท้จริงสำหรับคำถามของคุณ

แต่นี่คือคำตอบที่แท้จริง:

หากคุณกำลังกำหนดฟังก์ชันเหล่านี้ให้กับคุณสมบัติบางอย่างคุณสามารถรวมฟังก์ชันเดิมและแทนที่คุณสมบัติของคุณแทน:

// Original code in main.js
var theProperty = init;

function init(){
     doSomething();
}

// Extending it by replacing and wrapping, in extended.js
theProperty = (function(old) {
    function extendsInit() {
        old();
        doSomething();
    }

    return extendsInit;
})(theProperty);

หากฟังก์ชั่นของคุณไม่ได้อยู่บนวัตถุคุณอาจต้องการวางไว้ที่นั่นเพื่ออำนวยความสะดวกข้างต้น ตัวอย่างเช่น:

// In main.js
var MyLibrary = {
    init: function init() {
    }
};

// In extended.js
(function() {
    var oldInit = MyLibrary.init;
    MyLibrary.init = extendedInit;
    function extendedInit() {
        oldInit.call(MyLibrary); // Use #call in case `init` uses `this`
        doSomething();
    }
})();

แต่มีวิธีที่ดีกว่าในการทำเช่นนั้น เช่นให้วิธีการลงทะเบียนinitฟังก์ชัน

// In main.js
var MyLibrary = (function() {
    var initFunctions = [];
    return {
        init: function init() {
            var fns = initFunctions;
            initFunctions = undefined;
            for (var index = 0; index < fns.length; ++index) {
                try { fns[index](); } catch (e) { }
            }
        },
        addInitFunction: function addInitFunction(fn) {
            if (initFunctions) {
                // Init hasn't run yet, remember it
                initFunctions.push(fn);
            } else {
                // `init` has already run, call it almost immediately
                // but *asynchronously* (so the caller never sees the
                // call synchronously)
                setTimeout(fn, 0);
            }
        }
    };
})();

ที่นี่ในปี 2020 (หรือหลังจาก ~ 2016) ซึ่งสามารถเขียนได้กระชับขึ้นเล็กน้อย:

// In main.js
const MyLibrary = (() => {
    let initFunctions = [];
    return {
        init() {
            const fns = initFunctions;
            initFunctions = undefined;
            for (const fn of fns) {
                try { fn(); } catch (e) { }
            }
        },
        addInitFunction(fn) {
            if (initFunctions) {
                // Init hasn't run yet, remember it
                initFunctions.push(fn);
            } else {
                // `init` has already run, call it almost immediately
                // but *asynchronously* (so the caller never sees the
                // call synchronously)
                setTimeout(fn, 0);
                // Or: `Promise.resolve().then(() => fn());`
                // (Not `.then(fn)` just to avoid passing it an argument)
            }
        }
    };
})();

ขอบคุณสำหรับคำตอบที่ดี ปัญหาของฉันกับตัวอย่างที่สองคือฉันอาจต้องการผลลัพธ์จากฟังก์ชันที่ฉันกำลังขยาย
Gerhard Davids

64

มีหลายวิธีในการดำเนินการนี้ขึ้นอยู่กับวัตถุประสงค์ของคุณหากคุณต้องการเรียกใช้ฟังก์ชันนี้ด้วยและในบริบทเดียวกันคุณสามารถใช้.apply():

function init(){
  doSomething();
}
function myFunc(){
  init.apply(this, arguments);
  doSomethingHereToo();
}

หากคุณต้องการแทนที่ด้วยสิ่งที่ใหม่กว่าinitจะมีลักษณะดังนี้:

function init(){
  doSomething();
}
//anytime later
var old_init = init;
init = function() {
  old_init.apply(this, arguments);
  doSomethingHereToo();
};

2
บางครั้งคุณอาจต้องการวิธีการแทน.call .applyดูนี้คำถาม StackOverflow
MrDanA

@ นิกฉันพบว่าตัวอย่าง JavaScript ของคุณสำหรับการขยายฟังก์ชันที่มีอยู่มีประโยชน์มาก แต่ฉันอยากรู้ว่าสิ่งเดียวกันนี้จะทำผ่าน jQuery ได้อย่างไร?
Sunil

+1 ขอบคุณ สิ่งนี้มีประโยชน์มากหากคุณต้องการแก้ไขปลั๊กอินของบุคคลที่สามโดยไม่ต้องแก้ไข js ดั้งเดิม
GFoley83

1
ไม่แน่ใจว่าจะใช้กับฟังก์ชันที่คาดหวังพารามิเตอร์และส่งคืนค่าอย่างไร
Gerfried

5

วิธีอื่นนั้นยอดเยี่ยม แต่ไม่ได้เก็บรักษาฟังก์ชันต้นแบบใด ๆ ที่แนบมากับ init ในการหลีกเลี่ยงสิ่งนั้นคุณสามารถทำสิ่งต่อไปนี้ (ได้รับแรงบันดาลใจจากโพสต์จาก Nick Craver)

(function () {
    var old_prototype = init.prototype;
    var old_init = init;
    init = function () {
        old_init.apply(this, arguments);
        // Do something extra
    };
    init.prototype = old_prototype;
}) ();

5

อีกทางเลือกหนึ่งอาจเป็น:

var initial = function() {
    console.log( 'initial function!' );
}

var iWantToExecuteThisOneToo = function () {
    console.log( 'the other function that i wanted to execute!' );
}

function extendFunction( oldOne, newOne ) {
    return (function() {
        oldOne();
        newOne();
    })();
}

var extendedFunction = extendFunction( initial, iWantToExecuteThisOneToo );

1

โซลูชัน 2017+

แนวคิดของส่วนขยายฟังก์ชันมาจากกระบวนทัศน์การทำงานซึ่งได้รับการสนับสนุนโดยกำเนิดตั้งแต่ ES6:

function init(){
    doSomething();
}

// extend.js

init = (f => u => { f(u)
    doSomethingHereToo();
})(init);

init();

ตามความกังวลของ @ TJCrowder เกี่ยวกับ stack Dump เบราว์เซอร์จะจัดการกับสถานการณ์ได้ดีขึ้นมากในวันนี้ หากคุณบันทึกรหัสนี้ลงในtest.htmlและเรียกใช้คุณจะได้รับ

test.html:3 Uncaught ReferenceError: doSomething is not defined
    at init (test.html:3)
    at test.html:8
    at test.html:12

บรรทัดที่ 12: การเรียกเริ่มต้น, บรรทัดที่ 8: ส่วนขยาย init, บรรทัดที่ 3: การdoSomething()โทรที่ไม่ได้กำหนด

หมายเหตุ: TJ Crowder ผู้มีประสบการณ์เป็นอย่างมากที่กรุณาตอบคำถามของฉันเมื่อหลายปีก่อนตอนที่ฉันยังเป็นมือใหม่ หลายปีผ่านไปฉันยังคงจำท่าทีที่เคารพนับถือและพยายามทำตามแบบอย่างที่ดี


"ตามความกังวลของ @ TJCrowder เกี่ยวกับ stack Dump เบราว์เซอร์จะรับมือกับสถานการณ์ได้ดีกว่าในปัจจุบัน อันที่จริงและการปรับปรุงเหล่านั้นยังได้รับการเข้ารหัสในข้อกำหนด (ฉันยังสามารถหลีกเลี่ยงปัญหาที่ไม่ระบุตัวตนได้แม้ในปี 2011 ด้วยนิพจน์ฟังก์ชันที่ตั้งชื่อแม้ว่า IE จะทำให้ผิดใน IE8 และต่ำกว่า แต่วิธีที่ทำให้ผิดพลาดก็ไม่เป็นอันตรายอยู่และเรียนรู้ :-))
TJ Crowder

(และขอบคุณสำหรับความคิดเห็นที่กรุณาชื่นชมจริงๆ!)
TJ Crowder

0

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

ก่อนอื่นให้เราขยายฟังก์ชันจาวาสคริปต์

function Base(props) {
    const _props = props
    this.getProps = () => _props

    // We can make method private by not binding it to this object. 
    // Hence it is not exposed when we return this.
    const privateMethod = () => "do internal stuff" 

    return this
}

คุณสามารถขยายฟังก์ชันนี้ได้โดยสร้างฟังก์ชันลูกด้วยวิธีต่อไปนี้

function Child(props) {
    const parent = Base(props)
    this.getMessage = () => `Message is ${parent.getProps()}`;

    // You can remove the line below to extend as in private inheritance, 
    // not exposing parent function properties and method.
    this.prototype = parent
    return this
}

ตอนนี้คุณสามารถใช้ฟังก์ชัน Child ได้ดังนี้

let childObject = Child("Secret Message")
console.log(childObject.getMessage())     // logs "Message is Secret Message"
console.log(childObject.getProps())       // logs "Secret Message"

เรายังสามารถสร้าง Javascript Function ได้ด้วยการขยายคลาส Javascript เช่นนี้

class BaseClass {
    constructor(props) {
        this.props = props
        // You can remove the line below to make getProps method private. 
        // As it will not be binded to this, but let it be
        this.getProps = this.getProps.bind(this)
    }

    getProps() {
        return this.props
    }
}

ให้เราขยายคลาสนี้ด้วยฟังก์ชัน Child เช่นนี้

function Child(props) {
    let parent = new BaseClass(props)
    const getMessage = () => `Message is ${parent.getProps()}`;
    return { ...parent, getMessage} // I have used spread operator. 
}

อีกครั้งคุณสามารถใช้ฟังก์ชัน Child ดังต่อไปนี้เพื่อให้ได้ผลลัพธ์ที่คล้ายกัน

let childObject = Child("Secret Message")
console.log(childObject.getMessage())     // logs "Message is Secret Message"
console.log(childObject.getProps())       // logs "Secret Message"

Javascript เป็นภาษาที่ง่ายมาก เราทำได้เกือบทุกอย่าง Happy JavaScripting ... หวังว่าฉันจะสามารถให้ความคิดที่จะใช้ในกรณีของคุณ


-1

ใช้expandFunction.js

init = extendFunction(init, function(args) {
  doSomethingHereToo();
});

แต่ในกรณีเฉพาะของคุณการขยายฟังก์ชัน global onload จะง่ายกว่า:

extendFunction('onload', function(args) {
  doSomethingHereToo();
});

ฉันชอบคำถามของคุณมากมันทำให้ฉันคิดถึงกรณีการใช้งานที่แตกต่างกัน

สำหรับเหตุการณ์จาวาสคริปต์คุณต้องการเพิ่มและลบตัวจัดการ - แต่สำหรับการขยายฟังก์ชันคุณจะลบฟังก์ชันในภายหลังได้อย่างไร ฉันสามารถเพิ่มเมธอด. init = init.revert()ย้อนกลับไปยังฟังก์ชันเพิ่มเติมได้อย่างง่ายดายดังนั้นจะคืนฟังก์ชันเดิม เห็นได้ชัดว่าสิ่งนี้อาจนำไปสู่โค้ดที่ไม่ดี แต่บางทีมันอาจช่วยให้คุณทำบางสิ่งได้โดยไม่ต้องสัมผัสส่วนแปลกปลอมของ codebase

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