Menangani parameter opsional dalam javascript

127

Saya memiliki fungsi javascript statis yang dapat mengambil 1, 2 atau 3 parameter:

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

Saya tahu saya selalu dapat menguji apakah parameter yang diberikan tidak terdefinisi, tetapi bagaimana saya tahu jika yang dilewati adalah parameter atau panggilan balik?

Apa cara terbaik untuk melakukan ini?


Contoh dari apa yang bisa diteruskan:

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);
jd.
sumber
2
Bisakah Anda menunjukkan contoh dari apa yang bisa diteruskan?
James Black

Jawaban:

163

Anda dapat mengetahui berapa banyak argumen yang diteruskan ke fungsi Anda dan Anda dapat memeriksa apakah argumen kedua Anda adalah fungsi atau tidak:

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

Anda juga dapat menggunakan objek argumen dengan cara ini:

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];
  }
  //...
}

Jika Anda tertarik, coba lihat artikel ini oleh John Resig, tentang teknik untuk mensimulasikan metode kelebihan beban di JavaScript.

CMS
sumber
Mengapa menggunakan Object.prototype.toString.call (parameter) == "[Object Function]" dan bukan typeof (parameter) === 'function'? Apakah ada perbedaan penting di antara mereka? PS artikel yang Anda sebutkan tampaknya menggunakan yang terakhir
Tomer Cagan
@TomerCagan Saya pikir ini masalah preferensi (ish). Ada beberapa jawaban / komentar yang bagus tentang topik di bawah pertanyaan ini .
Philiiiiiipp
75

Er - itu akan menyiratkan bahwa Anda menjalankan fungsi Anda dengan argumen yang tidak dalam urutan yang tepat ... yang saya tidak akan merekomendasikan.

Saya akan merekomendasikan sebagai gantinya memberi makan objek ke fungsi Anda seperti:

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

Manfaat:

  • Anda tidak harus memperhitungkan urutan argumen
  • Anda tidak perlu melakukan pengecekan tipe
  • lebih mudah untuk menentukan nilai default karena tidak perlu memeriksa jenis
  • sakit kepala lebih sedikit. bayangkan jika Anda menambahkan argumen keempat, Anda harus memperbarui pemeriksaan tipe Anda setiap saat, dan bagaimana jika argumen keempat atau berturut-turut juga berfungsi?

Kekurangan:

  • waktu untuk kode refactor

Jika Anda tidak punya pilihan, Anda bisa menggunakan fungsi untuk mendeteksi apakah suatu objek memang sebuah fungsi (lihat contoh terakhir).

Catatan: Ini adalah cara yang tepat untuk mendeteksi suatu fungsi:

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

isFunction( function(){} )
meder omuraliev
sumber
"Ini akan bekerja 99% karena bug ES." Bisakah Anda menjelaskan lebih lanjut? Kenapa itu bisa salah?
jd.
Saya menambahkan kode yang benar untuk mendeteksi suatu fungsi. Saya percaya bug ada di sini: bugs.ecmascript.org/ticket/251
meder omuraliev
Saya sangat menyarankan Anda memberi makan hanya satu objek. Jika tidak, gunakan metode CMS.
meder omuraliev
Oh ... sial ... Baru saja memposting ide yang sama.
Arnis Lapsa
1
Satu kelemahan lain yang mungkin adalah kurangnya intellisense. Saya tidak menganggap ini masalah besar, tetapi harus diperhatikan.
Edyn
2

Anda harus memeriksa jenis parameter yang diterima. Mungkin Anda harus menggunakan argumentsarray karena parameter kedua kadang-kadang bisa menjadi 'parameter' dan kadang-kadang 'panggilan balik' dan menamai parameter itu mungkin menyesatkan.

Kamil Szot
sumber
2

Saya tahu ini adalah pertanyaan yang cukup lama, tetapi saya mengatasinya baru-baru ini. Beri tahu saya apa pendapat Anda tentang solusi ini.

Saya membuat sebuah utilitas yang memungkinkan saya mengetik argumen dan menjadikannya opsional. Anda pada dasarnya membungkus fungsi Anda dalam proxy. Jika Anda melewatkan argumen, itu tidak terdefinisi . Mungkin aneh jika Anda memiliki beberapa argumen opsional dengan jenis yang sama tepat di sebelah satu sama lain. (Ada opsi untuk melewati fungsi alih-alih jenis untuk melakukan pemeriksaan argumen khusus, serta menentukan nilai default untuk setiap parameter.)

Seperti inilah implementasinya:

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

Untuk kejelasan, inilah yang sedang terjadi:

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 ... */

           }
         );
};

Anda dapat melihat kode proxy arragrr di repositori GitHub saya di sini:

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

Berikut adalah fungsi utilitas dengan beberapa komentar yang disalin dari repositori:

/*
 ****** 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();
}
joelvh
sumber
2

Saya merekomendasikan Anda untuk menggunakan ArgueJS .

Anda cukup mengetik fungsi Anda dengan cara ini:

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

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

Saya menganggap dengan contoh Anda bahwa Anda ingin idparameter Anda menjadi string, bukan? Sekarang, getDatamembutuhkan String iddan menerima opsional Object parametersdan Function callback. Semua kasus penggunaan yang Anda poskan akan berfungsi seperti yang diharapkan.

zVictor
sumber
1

Maksud Anda, Anda dapat memiliki panggilan seperti ini: getData (id, parameter); getData (id, callback)?

Dalam hal ini Anda tidak dapat dengan jelas mengandalkan posisi dan Anda harus mengandalkan menganalisis tipe: getType () dan kemudian jika perlu getTypeName ()

Periksa apakah parameter yang dimaksud adalah array atau fungsi.

DmitryK
sumber
0

Saya pikir Anda ingin menggunakan typeof () di sini:

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"
Mark Bessey
sumber
0

Jika masalah Anda hanya dengan fungsi yang berlebihan (Anda perlu memeriksa apakah parameter 'parameter' adalah 'parameter' dan bukan 'callback'), saya akan merekomendasikan Anda tidak repot tentang jenis argumen dan
menggunakan pendekatan ini . Idenya sederhana - gunakan objek literal untuk menggabungkan parameter Anda:

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!");}
});
Arnis Lapsa
sumber
0

Saya kira ini mungkin contoh yang cukup jelas:

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);
}
ses
sumber
-5

Bisakah Anda mengganti fungsinya? Apakah ini tidak akan berhasil:

function doSomething(id){}
function doSomething(id,parameters){}
function doSomething(id,parameters,callback){}
J.Hendrix
sumber
8
Tidak, ini tidak akan berhasil. Anda tidak akan mendapatkan kesalahan apa pun, tetapi Javascript akan selalu menggunakan fungsi terbaru yang Anda tetapkan.
jd.
6
Wow. Saya pikir kamu gila. Saya baru saja mengujinya. Kamu benar. Dunia saya hanya sedikit berubah untuk saya. Saya pikir saya memiliki banyak JavaScript untuk dilihat hari ini untuk memastikan saya tidak memiliki yang seperti ini dalam produksi. Terima kasih atas komentarnya. Saya memberi Anda +1.
J.Hendrix