จะโอเวอร์โหลดฟังก์ชันในจาวาสคริปต์ได้อย่างไร?


104

วิธีการคลาสสิก (ไม่ใช่ js) ในการโอเวอร์โหลด:

function myFunc(){
 //code
}

function myFunc(overloaded){
 //other code
}

Javascript จะไม่อนุญาตให้กำหนดมากกว่าหนึ่งฟังก์ชันด้วยชื่อเดียวกัน ดังนั้นสิ่งต่างๆเช่นนี้จึงปรากฏขึ้น:

function myFunc(options){
 if(options["overloaded"]){
  //code
 }
}

มีวิธีแก้ปัญหาที่ดีกว่าสำหรับการทำงานมากเกินไปในจาวาสคริปต์นอกเหนือจากการส่งผ่านวัตถุที่มีโอเวอร์โหลดอยู่หรือไม่

การส่งผ่านในการโอเวอร์โหลดอาจทำให้ฟังก์ชันดูละเอียดเกินไปเนื่องจากการโอเวอร์โหลดแต่ละครั้งจะต้องมีคำสั่งเงื่อนไข การใช้ฟังก์ชั่นเพื่อบรรลุผล//codeภายในของคำสั่งเงื่อนไขเหล่านั้นอาจทำให้เกิดสถานการณ์ที่ยุ่งยากด้วยขอบเขต


1
แล้วคุณกำลังคิดเกี่ยวกับจาวาสคริปต์ผิด หากคุณมีปัญหาเกี่ยวกับการโอเวอร์โหลดและขอบเขตคุณควรมีชื่อวิธีการอื่นเนื่องจากดูเหมือนว่าจะทำงานอื่น
joshcomley

@joshcomley - โอเวอร์โหลดและขอบเขตต้องใช้รหัสในการจัดการ ฉันแค่พยายามตรวจสอบให้แน่ใจว่ารหัสที่ฉันใช้มีประสิทธิภาพมากที่สุด การจัดการขอบเขตด้วยวิธีแก้ปัญหาเป็นเรื่องปกติ แต่อย่างน้อยฉันก็อยากลองใช้วิธีการปฏิบัติที่ดีที่สุด
Travis J

คำตอบ:


126

มีหลายแง่มุมในการโต้แย้งการโอเวอร์โหลดใน Javascript:

  1. อาร์กิวเมนต์ตัวแปร - คุณสามารถส่งผ่านชุดของอาร์กิวเมนต์ที่แตกต่างกัน (ทั้งในประเภทและปริมาณ) และฟังก์ชันจะทำงานในลักษณะที่ตรงกับอาร์กิวเมนต์ที่ส่งผ่านไป

  2. อาร์กิวเมนต์เริ่มต้น - คุณสามารถกำหนดค่าเริ่มต้นสำหรับอาร์กิวเมนต์ได้หากไม่มีการส่งผ่าน

  3. อาร์กิวเมนต์ที่ตั้งชื่อ - ลำดับอาร์กิวเมนต์ไม่เกี่ยวข้องและคุณเพียงแค่ตั้งชื่ออาร์กิวเมนต์ที่คุณต้องการส่งผ่านไปยังฟังก์ชัน

ด้านล่างนี้เป็นหัวข้อเกี่ยวกับการจัดการอาร์กิวเมนต์แต่ละประเภทเหล่านี้

อาร์กิวเมนต์ตัวแปร

เนื่องจาก javascript ไม่มีการตรวจสอบประเภทของอาร์กิวเมนต์หรือจำนวนอาร์กิวเมนต์ที่ต้องการคุณสามารถมีการใช้งานเพียงครั้งเดียวmyFunc()ซึ่งสามารถปรับให้เข้ากับอาร์กิวเมนต์ที่ส่งผ่านไปได้โดยการตรวจสอบประเภทการมีอยู่หรือปริมาณของอาร์กิวเมนต์

jQuery ทำสิ่งนี้ตลอดเวลา คุณสามารถสร้างอาร์กิวเมนต์บางส่วนให้เป็นทางเลือกหรือคุณสามารถแตกแขนงในฟังก์ชันของคุณขึ้นอยู่กับว่าอาร์กิวเมนต์ใดถูกส่งไปยังอาร์กิวเมนต์นั้น

ในการใช้งานโอเวอร์โหลดประเภทนี้คุณมีเทคนิคต่างๆมากมายที่คุณสามารถใช้ได้:

  1. คุณสามารถตรวจสอบการมีอยู่ของอาร์กิวเมนต์ที่กำหนดโดยการตรวจสอบเพื่อดูว่าค่าชื่ออาร์กิวเมนต์ที่ประกาศคือundefinedหรือไม่
  2. arguments.lengthคุณสามารถตรวจสอบปริมาณรวมหรือข้อโต้แย้งกับ
  3. คุณสามารถตรวจสอบประเภทของอาร์กิวเมนต์ที่กำหนด
  4. สำหรับตัวเลขตัวแปรของการขัดแย้ง, คุณสามารถใช้argumentsหลอกอาร์เรย์ในการเข้าถึงการโต้แย้งใด ๆ arguments[i]ให้กับ

นี่คือตัวอย่างบางส่วน:

ลองดูobj.data()วิธีการของ jQuery รองรับการใช้งานสี่รูปแบบที่แตกต่างกัน:

obj.data("key");
obj.data("key", value);
obj.data();
obj.data(object);

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

นี่คือวิธีที่เราสามารถแยกแยะระหว่างตัวเลือกเหล่านี้ทั้งหมดเป็นภาษาอังกฤษจากนั้นฉันจะรวมเข้าด้วยกันทั้งหมดในรหัส:

// get the data element associated with a particular key value
obj.data("key");

หากอาร์กิวเมนต์แรกที่ส่งไปยัง.data()เป็นสตริงและอาร์กิวเมนต์ที่สองคือundefinedผู้เรียกต้องใช้แบบฟอร์มนี้


// set the value associated with a particular key
obj.data("key", value);

หากไม่ได้กำหนดอาร์กิวเมนต์ที่สองให้ตั้งค่าของคีย์เฉพาะ


// get all keys/values
obj.data();

หากไม่มีการส่งผ่านอาร์กิวเมนต์ให้ส่งคืนคีย์ / ค่าทั้งหมดในอ็อบเจ็กต์ที่ส่งคืน


// set all keys/values from the passed in object
obj.data(object);

หากประเภทของอาร์กิวเมนต์แรกเป็นวัตถุธรรมดาให้ตั้งค่าคีย์ / ค่าทั้งหมดจากวัตถุนั้น


นี่คือวิธีที่คุณสามารถรวมสิ่งเหล่านี้ทั้งหมดไว้ในตรรกะ javascript ชุดเดียว:

 // method declaration for .data()
 data: function(key, value) {
     if (arguments.length === 0) {
         // .data()
         // no args passed, return all keys/values in an object
     } else if (typeof key === "string") {
         // first arg is a string, look at type of second arg
         if (typeof value !== "undefined") {
             // .data("key", value)
             // set the value for a particular key
         } else {
             // .data("key")
             // retrieve a value for a key
         }
     } else if (typeof key === "object") {
         // .data(object)
         // set all key/value pairs from this object
     } else {
         // unsupported arguments passed
     }
 },

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

ตัวอย่างเช่นหากคุณมีฟังก์ชันที่รับอาร์กิวเมนต์สตริงสามตัว:

obj.query("firstArg", "secondArg", "thirdArg");

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

obj.query("firstArg", "secondArg");
obj.query("firstArg", "thirdArg");

เนื่องจากอาร์กิวเมนต์ทั้งสามเป็นประเภทเดียวกันคุณจึงไม่สามารถบอกความแตกต่างระหว่างอาร์กิวเมนต์ที่แตกต่างกันได้ดังนั้นคุณจึงไม่รู้ว่าผู้เรียกต้องการอะไร ด้วยรูปแบบการโทรนี้สามารถเลือกได้เฉพาะอาร์กิวเมนต์ที่สามเท่านั้น หากคุณต้องการเว้นอาร์กิวเมนต์ที่สองจะต้องส่งผ่านเป็นnull(หรือค่าอื่นที่ตรวจพบได้) แทนและโค้ดของคุณจะตรวจพบว่า:

obj.query("firstArg", null, "thirdArg");

นี่คือตัวอย่าง jQuery ของอาร์กิวเมนต์ที่เป็นทางเลือก อาร์กิวเมนต์ทั้งสองเป็นทางเลือกและรับค่าเริ่มต้นหากไม่ผ่าน:

clone: function( dataAndEvents, deepDataAndEvents ) {
    dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
    deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;

    return this.map( function () {
        return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
    });
},

นี่คือตัวอย่าง jQuery ที่อาร์กิวเมนต์สามารถหายไปหรือประเภทใดประเภทหนึ่งจากสามประเภทที่แตกต่างกันซึ่งทำให้คุณมีโอเวอร์โหลดที่แตกต่างกันสี่แบบ:

html: function( value ) {
    if ( value === undefined ) {
        return this[0] && this[0].nodeType === 1 ?
            this[0].innerHTML.replace(rinlinejQuery, "") :
            null;

    // See if we can take a shortcut and just use innerHTML
    } else if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
        (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) &&
        !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {

        value = value.replace(rxhtmlTag, "<$1></$2>");

        try {
            for ( var i = 0, l = this.length; i < l; i++ ) {
                // Remove element nodes and prevent memory leaks
                if ( this[i].nodeType === 1 ) {
                    jQuery.cleanData( this[i].getElementsByTagName("*") );
                    this[i].innerHTML = value;
                }
            }

        // If using innerHTML throws an exception, use the fallback method
        } catch(e) {
            this.empty().append( value );
        }

    } else if ( jQuery.isFunction( value ) ) {
        this.each(function(i){
            var self = jQuery( this );

            self.html( value.call(this, i, self.html()) );
        });

    } else {
        this.empty().append( value );
    }

    return this;
},

อาร์กิวเมนต์ที่มีชื่อ

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

นี่คือตัวอย่าง ES5 ง่ายๆ:

jQuery's $.ajax()ยอมรับรูปแบบการใช้งานที่คุณส่งผ่านพารามิเตอร์เดียวซึ่งเป็นวัตถุ Javascript ปกติที่มีคุณสมบัติและค่า คุณสมบัติใดที่คุณส่งผ่านเป็นตัวกำหนดว่าอาร์กิวเมนต์ / ตัวเลือกใดที่จะถูกส่งผ่านไปยังการเรียก ajax บางอย่างอาจจำเป็นหลายอย่างเป็นทางเลือก เนื่องจากเป็นคุณสมบัติของวัตถุจึงไม่มีคำสั่งเฉพาะ ในความเป็นจริงมีคุณสมบัติที่แตกต่างกันมากกว่า 30 คุณสมบัติที่สามารถส่งผ่านไปยังวัตถุนั้นได้โดยต้องมีเพียงหนึ่ง (url) เท่านั้น

นี่คือตัวอย่าง:

$.ajax({url: "http://www.example.com/somepath", data: myArgs, dataType: "json"}).then(function(result) {
    // process result here
});

ภายใน$.ajax()การนำไปใช้งานจากนั้นสามารถซักถามคุณสมบัติที่ส่งผ่านไปยังวัตถุขาเข้าและใช้เป็นอาร์กิวเมนต์ที่ตั้งชื่อ สิ่งนี้สามารถทำได้ทั้งด้วยfor (prop in obj)หรือโดยการรับคุณสมบัติทั้งหมดลงในอาร์เรย์โดยใช้อาร์เรย์Object.keys(obj)นั้นซ้ำ

เทคนิคนี้ใช้กันมากใน Javascript เมื่อมีอาร์กิวเมนต์จำนวนมากและ / หรืออาร์กิวเมนต์จำนวนมากเป็นทางเลือก หมายเหตุ: สิ่งนี้ให้ความสำคัญกับฟังก์ชันการนำไปใช้งานเพื่อให้แน่ใจว่ามีชุดอาร์กิวเมนต์ที่ถูกต้องน้อยที่สุดอยู่และเพื่อให้ข้อเสนอแนะในการแก้ไขข้อบกพร่องบางอย่างแก่ผู้โทรถึงสิ่งที่ขาดหายไปหากส่งผ่านอาร์กิวเมนต์ไม่เพียงพอ .

ในสภาพแวดล้อม ES6 สามารถใช้การทำลายโครงสร้างเพื่อสร้างคุณสมบัติ / ค่าเริ่มต้นสำหรับอ็อบเจ็กต์ที่ส่งผ่านข้างต้น นี้จะกล่าวถึงในรายละเอียดในบทความนี้อ้างอิง

นี่คือตัวอย่างหนึ่งจากบทความนั้น:

function selectEntries({ start=0, end=-1, step=1 } = {}) {
    ···
};

คุณสมบัตินี้จะสร้างการเริ่มต้นและค่าสำหรับstart, endและstepคุณสมบัติในวัตถุที่ส่งผ่านไปยังselectEntries()ฟังก์ชั่น

ค่าดีฟอลต์สำหรับอาร์กิวเมนต์ของฟังก์ชัน

ใน ES6 Javascript จะเพิ่มการรองรับภาษาในตัวสำหรับค่าเริ่มต้นสำหรับอาร์กิวเมนต์

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

function multiply(a, b = 1) {
  return a*b;
}

multiply(5); // 5

รายละเอียดเพิ่มเติมของวิธีการนี้สามารถนำมาใช้ที่นี่ใน MDN


ดังนั้นฉันควรยึดติดกับฟังก์ชั่นที่ปรับเปลี่ยนได้และยอมรับว่าเป็นแนวทางปฏิบัติที่ดีที่สุดสำหรับการนำฟังก์ชันโอเวอร์โหลดซุ้มไปใช้?
Travis J

2
@TravisJ - ใช่ฟังก์ชั่นที่ปรับเปลี่ยนได้คือวิธีการโอเวอร์โหลดใน Javascript ตัวเลือกอื่นของคุณคือการใช้ฟังก์ชันที่ตั้งชื่อแยกกันโดยมีอาร์กิวเมนต์ต่างกัน หากการใช้งานทั้งสองไม่มีอะไรเหมือนกันแสดงว่าเป็นแนวทางปฏิบัติที่ยอมรับได้ หากการใช้งานทั้งสองนั้นทำสิ่งเดียวกันเป็นหลัก แต่เพียงแค่เริ่มต้นด้วยข้อโต้แย้งที่แตกต่างกันฉันคิดว่ามันทำให้อินเทอร์เฟซของคุณมีขนาดกะทัดรัดมากขึ้นในการใช้ฟังก์ชันที่ปรับเปลี่ยนได้และเปิดโอกาสให้คุณแชร์โค้ดระหว่างการใช้งานทั้งสองที่เกี่ยวข้องกัน
jfriend00

เพิ่มตัวอย่างโค้ดโดยอธิบายทั้งในภาษาอังกฤษและโค้ดจริง
jfriend00

ฉันได้รับข้อผิดพลาดที่ไม่คาดคิดโทเค็น '=' ใน Safari resetProgressBar: function(display_errors, lockout = false)เวอร์ชันล่าสุดเมื่อฉันพยายามนี้
นิค

@Nick - ตามตารางความเข้ากันได้ของ ES6นี้ค่าพารามิเตอร์เริ่มต้นควรทำงานใน Safari 10 ขึ้นไป ต่อหน้า MDN นี้มันบอกว่า "ไม่มีการสนับสนุน" ใน Safari ฉันไม่มี Mac เลยไม่สามารถทดสอบด้วยตัวเองได้ โดยทั่วไปดูเหมือนว่าจะเป็นหนึ่งในคุณสมบัติ ES6 ที่นำมาใช้ในภายหลังในหลาย ๆ เบราว์เซอร์ ตัวอย่างเช่นดูเหมือนว่ายังไม่ได้อยู่ใน iOS9
jfriend00

33

การโอเวอร์โหลดฟังก์ชันใน JavaScript สามารถทำได้หลายวิธี ทั้งหมดเกี่ยวข้องกับฟังก์ชันหลักเดียวที่ดำเนินการกระบวนการทั้งหมดหรือมอบหมายให้กับฟังก์ชัน / กระบวนการย่อย

หนึ่งในเทคนิคง่ายๆที่พบบ่อยที่สุดคือการสลับง่ายๆ:

function foo(a, b) {
    switch (arguments.length) {
    case 0:
        //do basic code
        break;
    case 1:
        //do code with `a`
        break;
    case 2:
    default:
        //do code with `a` & `b`
        break;
    }
}

เทคนิคที่สวยงามกว่านั้นคือการใช้อาร์เรย์ (หรือวัตถุหากคุณไม่ได้ทำการโอเวอร์โหลดสำหรับทุกอาร์กิวเมนต์):

fooArr = [
    function () {
    },
    function (a) {
    },
    function (a,b) {
    }
];
function foo(a, b) {
    return fooArr[arguments.length](a, b);
}

ตัวอย่างก่อนหน้านั้นไม่สวยหรูใคร ๆ ก็สามารถแก้ไขfooArrได้และจะล้มเหลวหากมีคนส่งผ่านข้อโต้แย้งมากกว่า 2 ข้อfooดังนั้นรูปแบบที่ดีกว่าคือการใช้รูปแบบโมดูลและการตรวจสอบเล็กน้อย:

var foo = (function () {
    var fns;
    fns = [
        function () {
        },
        function (a) {
        },
        function (a, b) {
        }
    ];
    function foo(a, b) {
        var fnIndex;
        fnIndex = arguments.length;
        if (fnIndex > foo.length) {
            fnIndex = foo.length;
        }
        return fns[fnIndex].call(this, a, b);
    }
    return foo;
}());

แน่นอนว่าการโอเวอร์โหลดของคุณอาจต้องการใช้พารามิเตอร์จำนวนไดนามิกดังนั้นคุณสามารถใช้อ็อบเจ็กต์สำหรับfnsคอลเล็กชันได้

var foo = (function () {
    var fns;
    fns = {};
    fns[0] = function () {
    };
    fns[1] = function (a) {
    };
    fns[2] = function (a, b) {
    };
    fns.params = function (a, b /*, params */) {
    };
    function foo(a, b) {
        var fnIndex;
        fnIndex = arguments.length;
        if (fnIndex > foo.length) {
            fnIndex = 'params';
        }
        return fns[fnIndex].apply(this, Array.prototype.slice.call(arguments));
    }
    return foo;
}());

ความชอบส่วนตัวของฉันมีแนวโน้มที่จะเป็นswitchแม้ว่าจะมีฟังก์ชันหลักเป็นกลุ่ม ตัวอย่างทั่วไปที่ฉันใช้เทคนิคนี้จะเป็นวิธีการเข้าถึง / ตัวเปลี่ยน:

function Foo() {} //constructor
Foo.prototype = {
    bar: function (val) {
        switch (arguments.length) {
        case 0:
            return this._bar;
        case 1:
            this._bar = val;
            return this;
        }
    }
}

นี่เป็นตัวอย่างที่ดีของทางเลือก :)
Travis J

1
ดีฉันชอบเทคนิคที่ 2
Nick Rolando

8

คุณไม่สามารถทำวิธีการโอเวอร์โหลดได้ในแง่ที่เข้มงวด ไม่ชอบวิธีการที่จะได้รับการสนับสนุนในหรือjavac#

ปัญหาคือ JavaScript ไม่สนับสนุนวิธีการโอเวอร์โหลด ดังนั้นหากเห็น / แยกวิเคราะห์ฟังก์ชันตั้งแต่สองฟังก์ชันขึ้นไปที่มีชื่อเดียวกันก็จะพิจารณาฟังก์ชันที่กำหนดสุดท้ายและเขียนทับฟังก์ชันก่อนหน้านี้

วิธีหนึ่งที่ฉันคิดว่าเหมาะกับกรณีส่วนใหญ่มีดังนี้ -

บอกว่าคุณมีวิธีการ

function foo(x)
{
} 

แทนที่จะใช้วิธีการโอเวอร์โหลดซึ่งเป็นไปไม่ได้ในจาวาสคริปต์คุณสามารถกำหนดวิธีการใหม่ได้

fooNew(x,y,z)
{
}

จากนั้นปรับเปลี่ยนฟังก์ชันที่ 1 ดังนี้ -

function foo(x)
{
  if(arguments.length==2)
  {
     return fooNew(arguments[0],  arguments[1]);
  }
} 

หากคุณมีวิธีการที่มากเกินไปให้พิจารณาใช้switchมากกว่าif-elseคำสั่ง

( รายละเอียดเพิ่มเติม ) PS: ลิงก์ด้านบนไปที่บล็อกส่วนตัวของฉันซึ่งมีรายละเอียดเพิ่มเติมเกี่ยวกับเรื่องนี้


นั่นไม่ใช่ปัญหานั่นคือคุณสมบัติ
inetphantom

4

ฉันใช้วิธีการโอเวอร์โหลดที่แตกต่างกันเล็กน้อยตามหมายเลขอาร์กิวเมนต์ อย่างไรก็ตามฉันเชื่อว่าแนวทางของ John Fawcett ก็ดีเช่นกัน นี่คือตัวอย่างโค้ดตามคำอธิบายของ John Resig (jQuery's Author)

// o = existing object, n = function name, f = function.
    function overload(o, n, f){
        var old = o[n];
        o[n] = function(){
            if(f.length == arguments.length){
                return f.apply(this, arguments);
            }
            else if(typeof o == 'function'){
                return old.apply(this, arguments);
            }
        };
    }

การใช้งาน:

var obj = {};
overload(obj, 'function_name', function(){ /* what we will do if no args passed? */});
overload(obj, 'function_name', function(first){ /* what we will do if 1 arg passed? */});
overload(obj, 'function_name', function(first, second){ /* what we will do if 2 args passed? */});
overload(obj, 'function_name', function(first,second,third){ /* what we will do if 3 args passed? */});
//... etc :)

4

ใน javascript คุณสามารถใช้ฟังก์ชันเพียงครั้งเดียวและเรียกใช้ฟังก์ชันโดยไม่มีพารามิเตอร์myFunc() จากนั้นตรวจสอบเพื่อดูว่าตัวเลือกเป็น 'ไม่ได้กำหนด' หรือไม่

function myFunc(options){
 if(typeof options != 'undefined'){
  //code
 }
}

3

ผมพยายามที่จะพัฒนาวิธีการแก้ปัญหาที่สง่างามที่จะแก้ไขปัญหานี้ได้อธิบายไว้ที่นี่ และคุณจะพบการสาธิตที่นี่ ลักษณะการใช้งานจะเป็นดังนี้:

var out = def({
    'int': function(a) {
        alert('Here is int '+a);
    },

    'float': function(a) {
        alert('Here is float '+a);
    },

    'string': function(a) {
        alert('Here is string '+a);
    },

    'int,string': function(a, b) {
        alert('Here is an int '+a+' and a string '+b);
    },
    'default': function(obj) {
        alert('Here is some other value '+ obj);
    }

});

out('ten');
out(1);
out(2, 'robot');
out(2.5);
out(true);

วิธีการที่ใช้ในการบรรลุเป้าหมายนี้:

var def = function(functions, parent) {
 return function() {
    var types = [];
    var args = [];
    eachArg(arguments, function(i, elem) {
        args.push(elem);
        types.push(whatis(elem));
    });
    if(functions.hasOwnProperty(types.join())) {
        return functions[types.join()].apply(parent, args);
    } else {
        if (typeof functions === 'function')
            return functions.apply(parent, args);
        if (functions.hasOwnProperty('default'))
            return functions['default'].apply(parent, args);        
    }
  };
};

var eachArg = function(args, fn) {
 var i = 0;
 while (args.hasOwnProperty(i)) {
    if(fn !== undefined)
        fn(i, args[i]);
    i++;
 }
 return i-1;
};

var whatis = function(val) {

 if(val === undefined)
    return 'undefined';
 if(val === null)
    return 'null';

 var type = typeof val;

 if(type === 'object') {
    if(val.hasOwnProperty('length') && val.hasOwnProperty('push'))
        return 'array';
    if(val.hasOwnProperty('getDate') && val.hasOwnProperty('toLocaleTimeString'))
        return 'date';
    if(val.hasOwnProperty('toExponential'))
        type = 'number';
    if(val.hasOwnProperty('substring') && val.hasOwnProperty('length'))
        return 'string';
 }

 if(type === 'number') {
    if(val.toString().indexOf('.') > 0)
        return 'float';
    else
        return 'int';
 }

 return type;
};

1
ฟังก์ชั่นในการจัดการอินพุตฉลาดมาก
Travis J

3

https://github.com/jrf0110/leFunc

var getItems = leFunc({
  "string": function(id){
    // Do something
  },
  "string,object": function(id, options){
    // Do something else
  },
  "string,object,function": function(id, options, callback){
    // Do something different
    callback();
  },
  "object,string,function": function(options, message, callback){
    // Do something ca-raaaaazzzy
    callback();
  }
});

getItems("123abc"); // Calls the first function - "string"
getItems("123abc", {poop: true}); // Calls the second function - "string,object"
getItems("123abc", {butt: true}, function(){}); // Calls the third function - "string,object,function"
getItems({butt: true}, "What what?" function(){}); // Calls the fourth function - "object,string,function"

3

ไม่มีปัญหากับการโอเวอร์โหลดใน JS, pb จะรักษารหัสที่สะอาดเมื่อฟังก์ชันโอเวอร์โหลดได้อย่างไร?

คุณสามารถใช้การส่งต่อเพื่อให้มีรหัสที่สะอาดโดยพิจารณาจากสองสิ่ง:

  1. จำนวนอาร์กิวเมนต์ (เมื่อเรียกใช้ฟังก์ชัน)
  2. ประเภทของอาร์กิวเมนต์ (เมื่อเรียกใช้ฟังก์ชัน)

      function myFunc(){
          return window['myFunc_'+arguments.length+Array.from(arguments).map((arg)=>typeof arg).join('_')](...arguments);
       }
    
        /** one argument & this argument is string */
      function myFunc_1_string(){
    
      }
       //------------
       /** one argument & this argument is object */
      function myFunc_1_object(){
    
      }
      //----------
      /** two arguments & those arguments are both string */
      function myFunc_2_string_string(){
    
      }
       //--------
      /** Three arguments & those arguments are : id(number),name(string), callback(function) */
      function myFunc_3_number_string_function(){
                let args=arguments;
                  new Person(args[0],args[1]).onReady(args[3]);
      }
    
       //--- And so on ....   

2

ลองดู:

http://www.codeproject.com/Articles/688869/Overloading-JavaScript-Functions

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


2

เนื่องจาก JavaScript ไม่มีฟังก์ชัน overload option objectจึงสามารถใช้แทนได้ หากมีอาร์กิวเมนต์ที่จำเป็นหนึ่งหรือสองข้อควรแยกจากอ็อบเจ็กต์อ็อพชัน นี่คือตัวอย่างวิธีใช้อ็อบเจ็กต์อ็อพชันและค่าที่เติมเป็นค่าดีฟอลต์ในกรณีที่ไม่ได้ส่งค่าในอ็อบเจ็กต์อ็อพชัน

function optionsObjectTest(x, y, opts) {
    opts = opts || {}; // default to an empty options object

    var stringValue = opts.stringValue || "string default value";
    var boolValue = !!opts.boolValue; // coerces value to boolean with a double negation pattern
    var numericValue = opts.numericValue === undefined ? 123 : opts.numericValue;

    return "{x:" + x + ", y:" + y + ", stringValue:'" + stringValue + "', boolValue:" + boolValue + ", numericValue:" + numericValue + "}";

}

นี่คือตัวอย่างวิธีใช้อ็อบเจกต์ตัวเลือก


2

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

<script > 
//Main function to add the methods
function addMethod(object, name, fn) {
  var old = object[name];
  object[name] = function(){
    if (fn.length == arguments.length)
      return fn.apply(this, arguments)
    else if (typeof old == 'function')
      return old.apply(this, arguments);
  };
}


  var ninjas = {
   values: ["Dean Edwards", "Sam Stephenson", "Alex Russell"]
};

//Here we declare the first function with no arguments passed
  addMethod(ninjas, "find", function(){
    return this.values;
});

//Second function with one argument
  addMethod(ninjas, "find", function(name){
    var ret = [];
    for (var i = 0; i < this.values.length; i++)
      if (this.values[i].indexOf(name) == 0)
        ret.push(this.values[i]);
    return ret;
  });

//Third function with two arguments
  addMethod(ninjas, "find", function(first, last){
    var ret = [];
    for (var i = 0; i < this.values.length; i++)
      if (this.values[i] == (first + " " + last))
        ret.push(this.values[i]);
    return ret;
  });


//Now you can do:
ninjas.find();
ninjas.find("Sam");
ninjas.find("Dean", "Edwards")
</script>

0

ฉันต้องการเพิ่มฟังก์ชันย่อยภายในฟังก์ชันหลักเพื่อให้เกิดความสามารถในการแยกความแตกต่างระหว่างกลุ่มอาร์กิวเมนต์สำหรับฟังก์ชันเดียวกัน

var doSomething = function() {
    var foo;
    var bar;
};

doSomething.withArgSet1 = function(arg0, arg1) {
    var obj = new doSomething();
    // do something the first way
    return obj;
};

doSomething.withArgSet2 = function(arg2, arg3) {
    var obj = new doSomething();
    // do something the second way
    return obj;
};

0

สิ่งที่คุณพยายามจะบรรลุทำได้ดีที่สุดโดยใช้ตัวแปรอาร์กิวเมนต์ภายในของฟังก์ชัน

function foo() {
    if (arguments.length === 0) {
        //do something
    }
    if (arguments.length === 1) {
        //do something else
    }
}

foo(); //do something
foo('one'); //do something else

คุณสามารถหาคำอธิบายที่ดีขึ้นของวิธีการทำงานนี้ที่นี่


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