การจัดการพารามิเตอร์ที่เป็นทางเลือกในจาวาสคริปต์


127

ฉันมีฟังก์ชันจาวาสคริปต์แบบคงที่ที่สามารถรับพารามิเตอร์ 1, 2 หรือ 3:

function getData(id, parameters, callback) //parameters (associative array) and callback (function) are optional

ฉันรู้ว่าฉันสามารถทดสอบได้เสมอว่าพารามิเตอร์ที่กำหนดนั้นไม่ได้กำหนดไว้หรือไม่ แต่ฉันจะรู้ได้อย่างไรว่าสิ่งที่ส่งผ่านนั้นเป็นพารามิเตอร์หรือการเรียกกลับ

วิธีที่ดีที่สุดในการทำเช่นนี้คืออะไร?


ตัวอย่างของสิ่งที่สามารถส่งผ่านได้:

1:

getData('offers');

2:

var array = new Array();
array['type']='lalal';
getData('offers',array);

3:

var foo = function (){...}
getData('offers',foo);

4:

getData('offers',array,foo);

2
คุณสามารถแสดงตัวอย่างของสิ่งที่สามารถส่งผ่านได้หรือไม่?
James Black

คำตอบ:


163

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

function getData (id, parameters, callback) {
  if (arguments.length == 2) { // if only two arguments were supplied
    if (Object.prototype.toString.call(parameters) == "[object Function]") {
      callback = parameters; 
    }
  }
  //...
}

คุณยังสามารถใช้ออบเจ็กต์อาร์กิวเมนต์ได้ด้วยวิธีนี้:

function getData (/*id, parameters, callback*/) {
  var id = arguments[0], parameters, callback;

  if (arguments.length == 2) { // only two arguments supplied
    if (Object.prototype.toString.call(arguments[1]) == "[object Function]") {
      callback = arguments[1]; // if is a function, set as 'callback'
    } else {
      parameters = arguments[1]; // if not a function, set as 'parameters'
    }
  } else if (arguments.length == 3) { // three arguments supplied
      parameters = arguments[1];
      callback = arguments[2];
  }
  //...
}

หากคุณสนใจลองอ่านบทความนี้โดย John Resig เกี่ยวกับเทคนิคในการจำลองวิธีการที่ใช้ JavaScript มากเกินไป


เหตุใดจึงใช้ Object.prototype.toString.call (parameters) == "[object Function]" และไม่ใช่ typeof (parameters) === 'function' มีความแตกต่างที่สำคัญระหว่างพวกเขาหรือไม่? ป.ล. บทความที่คุณพูดถึงดูเหมือนจะใช้หลัง
Tomer Cagan

@TomerCagan ฉันคิดว่ามันเป็นเรื่องของความชอบ (ish) มีบางคำตอบที่ดีการแสดงความคิดเห็นในหัวข้อภายใต้การมี / นี้คำถาม
Philiiiiiipp

75

เอ่อ - นั่นหมายความว่าคุณกำลังเรียกใช้ฟังก์ชันของคุณด้วยอาร์กิวเมนต์ที่ไม่ได้อยู่ในลำดับที่เหมาะสม ... ซึ่งฉันไม่แนะนำ

ฉันอยากจะแนะนำให้ป้อนวัตถุให้กับฟังก์ชันของคุณแทนดังนี้:

function getData( props ) {
    props = props || {};
    props.params = props.params || {};
    props.id = props.id || 1;
    props.callback = props.callback || function(){};
    alert( props.callback )
};

getData( {
    id: 3,
    callback: function(){ alert('hi'); }
} );

ประโยชน์ที่ได้รับ:

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

ข้อเสีย:

  • เวลาในการ refactor รหัส

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

หมายเหตุ: นี่เป็นวิธีที่เหมาะสมในการตรวจจับฟังก์ชัน:

function isFunction(obj) {
    return Object.prototype.toString.call(obj) === "[object Function]";
}

isFunction( function(){} )

"สิ่งนี้จะทำงานได้ 99% เนื่องจากข้อบกพร่องของ ES" คุณช่วยอธิบายเพิ่มเติมได้ไหม ทำไมมันผิดพลาด?
jd.

ฉันได้เพิ่มรหัสที่ถูกต้องเพื่อตรวจหาฟังก์ชัน ฉันเชื่อว่าจุดบกพร่องอยู่ที่นี่: bugs.ecmascript.org/ticket/251
meder omuraliev

ฉันขอแนะนำให้คุณป้อนวัตถุเพียงชิ้นเดียว ถ้าไม่ใช้วิธีการของ CMS
meder omuraliev

โธ่ ... ไอ้ ... แค่โพสต์ไอเดียเดียวกัน
Arnis Lapsa

1
ข้อเสียเปรียบอีกประการหนึ่งที่เป็นไปได้คือการขาด intellisense ฉันไม่ถือว่าเรื่องนี้เป็นเรื่องใหญ่ แต่ควรสังเกต
Edyn

2

ดังนั้นใช้ตัวดำเนินการ typeof เพื่อตรวจสอบว่าพารามิเตอร์ที่สองเป็น Array หรือฟังก์ชัน

สิ่งนี้สามารถให้คำแนะนำ: http://www.planetpdf.com/developer/article.asp?ContentID=testing_for_object_types_in_ja

ฉันไม่แน่ใจว่านี่คืองานหรือการบ้านดังนั้นฉันจึงไม่ต้องการให้คำตอบกับคุณในขณะนี้ แต่ typeof จะช่วยคุณระบุได้


2

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


2

ฉันรู้ว่านี่เป็นคำถามเก่า ๆ แต่ฉันเพิ่งจัดการกับเรื่องนี้ แจ้งให้เราทราบว่าคุณคิดอย่างไรกับโซลูชันนี้

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

นี่คือลักษณะการใช้งาน:

function displayOverlay(/*message, timeout, callback*/) {
  return arrangeArgs(arguments, String, Number, Function, 
    function(message, timeout, callback) {
      /* ... your code ... */
    });
};

เพื่อความชัดเจนนี่คือสิ่งที่เกิดขึ้น:

function displayOverlay(/*message, timeout, callback*/) {
  //arrangeArgs is the proxy
  return arrangeArgs(
           //first pass in the original arguments
           arguments, 
           //then pass in the type for each argument
           String,  Number,  Function, 
           //lastly, pass in your function and the proxy will do the rest!
           function(message, timeout, callback) {

             //debug output of each argument to verify it's working
             console.log("message", message, "timeout", timeout, "callback", callback);

             /* ... your code ... */

           }
         );
};

คุณสามารถดูรหัสพร็อกซี ArrangeArgs ในที่เก็บ GitHub ของฉันได้ที่นี่:

https://github.com/joelvh/Sysmo.js/blob/master/sysmo.js

นี่คือฟังก์ชันยูทิลิตี้ที่มีความคิดเห็นบางส่วนคัดลอกมาจากที่เก็บ:

/*
 ****** Overview ******
 * 
 * Strongly type a function's arguments to allow for any arguments to be optional.
 * 
 * Other resources:
 * http://ejohn.org/blog/javascript-method-overloading/
 * 
 ****** Example implementation ******
 * 
 * //all args are optional... will display overlay with default settings
 * var displayOverlay = function() {
 *   return Sysmo.optionalArgs(arguments, 
 *            String, [Number, false, 0], Function, 
 *            function(message, timeout, callback) {
 *              var overlay = new Overlay(message);
 *              overlay.timeout = timeout;
 *              overlay.display({onDisplayed: callback});
 *            });
 * }
 * 
 ****** Example function call ******
 * 
 * //the window.alert() function is the callback, message and timeout are not defined.
 * displayOverlay(alert);
 * 
 * //displays the overlay after 500 miliseconds, then alerts... message is not defined.
 * displayOverlay(500, alert);
 * 
 ****** Setup ******
 * 
 * arguments = the original arguments to the function defined in your javascript API.
 * config = describe the argument type
 *  - Class - specify the type (e.g. String, Number, Function, Array) 
 *  - [Class/function, boolean, default] - pass an array where the first value is a class or a function...
 *                                         The "boolean" indicates if the first value should be treated as a function.
 *                                         The "default" is an optional default value to use instead of undefined.
 * 
 */
arrangeArgs: function (/* arguments, config1 [, config2] , callback */) {
  //config format: [String, false, ''], [Number, false, 0], [Function, false, function(){}]
  //config doesn't need a default value.
  //config can also be classes instead of an array if not required and no default value.

  var configs = Sysmo.makeArray(arguments),
      values = Sysmo.makeArray(configs.shift()),
      callback = configs.pop(),
      args = [],
      done = function() {
        //add the proper number of arguments before adding remaining values
        if (!args.length) {
          args = Array(configs.length);
        }
        //fire callback with args and remaining values concatenated
        return callback.apply(null, args.concat(values));
      };

  //if there are not values to process, just fire callback
  if (!values.length) {
    return done();
  }

  //loop through configs to create more easily readable objects
  for (var i = 0; i < configs.length; i++) {

    var config = configs[i];

    //make sure there's a value
    if (values.length) {

      //type or validator function
      var fn = config[0] || config,
          //if config[1] is true, use fn as validator, 
          //otherwise create a validator from a closure to preserve fn for later use
          validate = (config[1]) ? fn : function(value) {
            return value.constructor === fn;
          };

      //see if arg value matches config
      if (validate(values[0])) {
        args.push(values.shift());
        continue;
      }
    }

    //add a default value if there is no value in the original args
    //or if the type didn't match
    args.push(config[2]);
  }

  return done();
}

2

ผมขอแนะนำให้คุณใช้ArgueJS

คุณสามารถพิมพ์ฟังก์ชันของคุณด้วยวิธีนี้:

function getData(){
  arguments = __({id: String, parameters: [Object], callback: [Function]})

  // and now access your arguments by arguments.id,
  //          arguments.parameters and arguments.callback
}

ฉันพิจารณาจากตัวอย่างของคุณแล้วว่าคุณต้องการให้idพารามิเตอร์เป็นสตริงใช่ไหม ตอนนี้getDataกำลังต้องการString idและกำลังยอมรับตัวเลือกObject parametersและFunction callback. กรณีการใช้งานทั้งหมดที่คุณโพสต์จะทำงานตามที่คาดไว้



1

คุณกำลังบอกว่าคุณสามารถเรียกได้ดังนี้ getData (id พารามิเตอร์); getData (id โทรกลับ)?

ในกรณีนี้คุณไม่สามารถพึ่งพาตำแหน่งได้อย่างชัดเจนและคุณต้องพึ่งพาการวิเคราะห์ประเภท: getType () และถ้าจำเป็น getTypeName ()

ตรวจสอบว่าพารามิเตอร์ที่เป็นปัญหาเป็นอาร์เรย์หรือฟังก์ชัน


0

ฉันคิดว่าคุณต้องการใช้ typeof () ที่นี่:

function f(id, parameters, callback) {
  console.log(typeof(parameters)+" "+typeof(callback));
}

f("hi", {"a":"boo"}, f); //prints "object function"
f("hi", f, {"a":"boo"}); //prints "function object"

0

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

function getData(id, opt){
    var data = voodooMagic(id, opt.parameters);
    if (opt.callback!=undefined)
      opt.callback.call(data);
    return data;         
}

getData(5, {parameters: "1,2,3", callback: 
    function(){for (i=0;i<=1;i--)alert("FAIL!");}
});

0

ฉันเดาว่าอาจเป็นตัวอย่างที่อธิบายตนเองได้:

function clickOn(elem /*bubble, cancelable*/) {
    var bubble =     (arguments.length > 1)  ? arguments[1] : true;
    var cancelable = (arguments.length == 3) ? arguments[2] : true;

    var cle = document.createEvent("MouseEvent");
    cle.initEvent("click", bubble, cancelable);
    elem.dispatchEvent(cle);
}

-5

คุณสามารถแทนที่ฟังก์ชันได้หรือไม่? สิ่งนี้จะไม่ทำงาน:

function doSomething(id){}
function doSomething(id,parameters){}
function doSomething(id,parameters,callback){}

8
ไม่มันใช้ไม่ได้ คุณจะไม่ได้รับข้อผิดพลาดใด ๆ แต่ Javascript จะใช้ฟังก์ชันล่าสุดที่คุณกำหนดไว้เสมอ
jd.

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