javascript - функции - замыкания




Есть ли лучший способ делать необязательные параметры функции в JavaScript? (19)

Я всегда обрабатывал необязательные параметры в JavaScript:

function myFunc(requiredArg, optionalArg){
  optionalArg = optionalArg || 'defaultValue';

  // Do stuff
}

Есть ли лучший способ сделать это?

Существуют ли случаи, когда использование || как это провалится?


  1. arg || 'default' arg || 'default' - отличный способ и работает в 90% случаев

  2. Он терпит неудачу, когда вам нужно передать значения, которые могут быть «ложными»,

    • false
    • 0
    • NaN
    • ""

    Для этих случаев вам нужно быть немного более подробным и проверить undefined

  3. Также будьте осторожны, когда у вас есть дополнительные аргументы в первую очередь, вы должны знать о типах всех ваших аргументов


В ECMAScript 2015 (иначе ES6) вы можете объявить значения аргументов по умолчанию в объявлении функции:

function myFunc(requiredArg, optionalArg = 'defaultValue') {
    // do stuff
}

Подробнее о них в этой статье о MDN (несмотря на название статьи, они называются «аргументы», а не «параметры» в JavaScript).

В настоящее время это поддерживается только Firefox , но по мере того, как стандарт был завершен, ожидайте, что поддержка будет быстро улучшаться.


Ваша логика завершается с ошибкой, если optionalArg передается, но оценивается как false - попробуйте это как альтернативу

if (typeof optionalArg === 'undefined') { optionalArg = 'default'; }

Или альтернативная идиома:

optionalArg = (typeof optionalArg === 'undefined') ? 'default' : optionalArg;

Используйте любую идиому, которая связывает ваше намерение лучше всего!


Во время проекта я заметил, что слишком много повторяю с дополнительными параметрами и настройками, поэтому я создал класс, который обрабатывает проверку типов и присваивает значение по умолчанию, которое приводит к аккуратным и читаемым кодам. См. Пример и дайте мне знать, если это сработает для вас.

var myCar           = new Car('VW', {gearbox:'automatic', options:['radio', 'airbags 2x']});
var myOtherCar      = new Car('Toyota');

function Car(brand, settings) {
    this.brand      = brand;

    // readable and adjustable code
    settings        = DefaultValue.object(settings, {});
    this.wheels     = DefaultValue.number(settings.wheels, 4);
    this.hasBreaks  = DefaultValue.bool(settings.hasBreaks, true);
    this.gearbox    = DefaultValue.string(settings.gearbox, 'manual');
    this.options    = DefaultValue.array(settings.options, []);

    // instead of doing this the hard way
    settings        = settings || {};
    this.wheels     = (!isNaN(settings.wheels)) ? settings.wheels : 4;
    this.hasBreaks  = (typeof settings.hasBreaks !== 'undefined') ? (settings.hasBreaks === true) : true;
    this.gearbox    = (typeof settings.gearbox === 'string') ? settings.gearbox : 'manual';
    this.options    = (typeof settings.options !== 'undefined' && Array.isArray(settings.options)) ? settings.options : [];
}

Использование этого класса:

(function(ns) {

    var DefaultValue = {

        object: function(input, defaultValue) {
            if (typeof defaultValue !== 'object') throw new Error('invalid defaultValue type');
            return (typeof input !== 'undefined') ? input : defaultValue;
        },

        bool: function(input, defaultValue) {
            if (typeof defaultValue !== 'boolean') throw new Error('invalid defaultValue type');
            return (typeof input !== 'undefined') ? (input === true) : defaultValue;
        },

        number: function(input, defaultValue) {
            if (isNaN(defaultValue)) throw new Error('invalid defaultValue type');
            return (typeof input !== 'undefined' && !isNaN(input)) ? parseFloat(input) : defaultValue;
        },

        // wrap the input in an array if it is not undefined and not an array, for your convenience
        array: function(input, defaultValue) {
            if (typeof defaultValue === 'undefined') throw new Error('invalid defaultValue type');
            return (typeof input !== 'undefined') ? (Array.isArray(input) ? input : [input]) : defaultValue;
        },

        string: function(input, defaultValue) {
            if (typeof defaultValue !== 'string') throw new Error('invalid defaultValue type');
            return (typeof input === 'string') ? input : defaultValue;
        },

    };

    ns.DefaultValue = DefaultValue;

}(this));

Вы можете использовать для этого несколько разных схем. Я всегда тестировал аргументы.length:

function myFunc(requiredArg, optionalArg){
  optionalArg = myFunc.arguments.length<2 ? 'defaultValue' : optionalArg;

  ...

- делая это, он не может потерпеть неудачу, но я не знаю, есть ли у вашего пути какие-либо шансы на неудачу, просто сейчас я не могу придумать сценарий, где он действительно потерпит неудачу ...

И тогда Павел предоставил один неудачный сценарий! -)


Если вам нужно выбить буквальный NULL , тогда у вас могут возникнуть проблемы. Кроме того, нет, я думаю, что вы, вероятно, на правильном пути.

Другой метод, который выбирают некоторые люди, - это перенос массива переменных, итерации через список аргументов. Он выглядит немного опрятным, но я думаю, что это немного (очень мало) бит больше процесса / памяти.

function myFunction (argArray) {
    var defaults = {
        'arg1'  :   "value 1",
        'arg2'  :   "value 2",
        'arg3'  :   "value 3",
        'arg4'  :   "value 4"
    }

    for(var i in defaults) 
        if(typeof argArray[i] == "undefined") 
               argArray[i] = defaults[i];

    // ...
}

Если вы используете по умолчанию широко, это выглядит гораздо более читаемым:

function usageExemple(a,b,c,d){
    //defaults
    a=defaultValue(a,1);
    b=defaultValue(b,2);
    c=defaultValue(c,4);
    d=defaultValue(d,8);

    var x = a+b+c+d;
    return x;
}

Просто объявите эту функцию на глобальном экране.

function defaultValue(variable,defaultValue){
    return(typeof variable!=='undefined')?(variable):(defaultValue);
}

Рисунок использования fruit = defaultValue(fruit,'Apple');

* PS вы можете переименовать функцию defaultValue в короткое имя, просто не используйте default , это зарезервированное слово в javascript.


Исправьте меня, если я ошибаюсь, но это похоже на самый простой способ (по одному аргументу, так или иначе):

function myFunction(Required,Optional)
{
    if (arguments.length<2) Optional = "Default";
    //Your code
}

Как и ответ Оли, я использую аргумент Object и Object, который определяет значения по умолчанию. С немного сахара ...

/**
 * Updates an object's properties with other objects' properties. All
 * additional non-falsy arguments will have their properties copied to the
 * destination object, in the order given.
 */
function extend(dest) {
  for (var i = 1, l = arguments.length; i < l; i++) {
    var src = arguments[i]
    if (!src) {
      continue
    }
    for (var property in src) {
      if (src.hasOwnProperty(property)) {
        dest[property] = src[property]
      }
    }
  }
  return dest
}

/**
 * Inherit another function's prototype without invoking the function.
 */
function inherits(child, parent) {
  var F = function() {}
  F.prototype = parent.prototype
  child.prototype = new F()
  child.prototype.constructor = child
  return child
}

... это можно сделать немного лучше.

function Field(kwargs) {
  kwargs = extend({
    required: true, widget: null, label: null, initial: null,
    helpText: null, errorMessages: null
  }, kwargs)
  this.required = kwargs.required
  this.label = kwargs.label
  this.initial = kwargs.initial
  // ...and so on...
}

function CharField(kwargs) {
  kwargs = extend({
    maxLength: null, minLength: null
  }, kwargs)
  this.maxLength = kwargs.maxLength
  this.minLength = kwargs.minLength
  Field.call(this, kwargs)
}
inherits(CharField, Field)

Что хорошего в этом методе?

  • Вы можете опустить столько аргументов, сколько хотите - если вы хотите только переопределить значение одного аргумента, вы можете просто предоставить этот аргумент вместо того, чтобы явно пропускать undefined когда, скажем, есть 5 аргументов, и вы хотите только настроить последний, как вам пришлось бы делать с некоторыми из других предложенных методов.
  • При работе с конструктором Function для объекта, который наследуется от другого, легко принять любые аргументы, которые требуются конструктору объекта, на который вы наследуете, поскольку вам не нужно называть эти аргументы в вашей сигнатуре конструктора, или даже предоставить свои собственные значения по умолчанию (пусть конструктор родительского объекта сделает это для вас, как показано выше, когда CharField вызывает конструктор Field ).
  • Дочерние объекты в иерархиях наследования могут настраивать аргументы для своего родительского конструктора по своему усмотрению, применяя собственные значения по умолчанию или гарантируя, что определенное значение всегда будет использоваться.

Люди -

Посмотрев на эти и другие решения, я попробовал несколько из них, используя фрагмент кода, первоначально из W3Schools, в качестве базы. Вы можете найти, что работает в следующем. Каждый из элементов, закомментированных, также работает, и вы можете просто поэкспериментировать, удалив отдельные комментарии. Чтобы быть ясным, параметр «eyecolor» не определяется.

function person(firstname, lastname, age, eyecolor)
{
this.firstname = firstname;
this.lastname = lastname;
this.age = age;
this.eyecolor = eyecolor;
// if(null==eyecolor)
//   this.eyecolor = "unknown1";
//if(typeof(eyecolor)==='undefined') 
//   this.eyecolor = "unknown2";
// if(!eyecolor)
//   this.eyecolor = "unknown3";
this.eyecolor = this.eyecolor || "unknown4";
}

var myFather = new person("John", "Doe", 60);
var myMother = new person("Sally", "Rally", 48, "green");

var elem = document.getElementById("demo");
elem.innerHTML = "My father " +
              myFather.firstname + " " +
              myFather.lastname + " is " +
              myFather.age + " with " +
              myFather.eyecolor + " eyes.<br/>" +
              "My mother " +
              myMother.firstname + " " +
              myMother.lastname + " is " +
              myMother.age + " with " +
              myMother.eyecolor + " eyes."; 

Попав в этот вопрос, поиск параметров по умолчанию в EcmaScript 2015 , поэтому просто упоминается ...

С ES6 мы можем выполнять параметры по умолчанию :

function doSomething(optionalParam = "defaultValue"){
    console.log(optionalParam);//not required to check for falsy values
}

doSomething(); //"defaultValue"
doSomething("myvalue"); //"myvalue"

С ES2015 / ES6 вы можете использовать Object.assign который может заменить $.extend() или _.defaults()

function myFunc(requiredArg, options = {}) {
  const defaults = {
    message: 'Hello',
    color: 'red',
    importance: 1
  };

  const settings = Object.assign({}, defaults, options);

  // do stuff
}

Вы также можете использовать аргументы по умолчанию, подобные этому

function myFunc(requiredArg, { message: 'Hello', color: 'red', importance: 1 } = {}) {
  // do stuff
}

Это то, с чем я столкнулся:

function WhoLikesCake(options) {
  options = options || {};
  var defaultOptions = {
    a : options.a || "Huh?",
    b : options.b || "I don't like cake."
  }
  console.log('a: ' + defaultOptions.b + ' - b: ' + defaultOptions.b);

  // Do more stuff here ...
}

Вызывается следующим образом:

WhoLikesCake({ b : "I do" });

Я не знаю, почему @ ответ Павла вниз, но валидация против null является хорошим выбором. Может быть, более позитивный пример будет иметь смысл:

В JavaScript пропущенный параметр похож на объявленную переменную, которая не инициализирована (просто var a1; ). И оператор равенства преобразует неопределенное значение в null, поэтому это отлично работает с обоими типами значений и объектами, и именно так CoffeeScript обрабатывает необязательные параметры.

function overLoad(p1){
    alert(p1 == null); // Caution, don't use the strict comparison: === won't work.
    alert(typeof p1 === 'undefined');
}

overLoad(); // true, true
overLoad(undefined); // true, true. Yes, undefined is treated as null for equality operator.
overLoad(10); // false, false


function overLoad(p1){
    if (p1 == null) p1 = 'default value goes here...';
    //...
}

Хотя есть опасения, что для лучшей семантики есть typeof variable === 'undefined' немного лучше. Я не собираюсь защищать это, так как это касается базового API, как реализована функция; он не должен интересовать пользователя API.

Я также должен добавить, что здесь единственный способ физически удостовериться, что любой аргумент был пропущен, используя оператор in который, к сожалению, не будет работать с именами параметров, должен пройти индекс arguments .

function foo(a, b) {
    // Both a and b will evaluate to undefined when used in an expression
    alert(a); // undefined
    alert(b); // undefined

    alert("0" in arguments); // true
    alert("1" in arguments); // false
}

foo (undefined);

Я привык видеть несколько основных вариантов обработки необязательных переменных. Иногда расслабляющие версии полезны.

function foo(a, b, c) {
  a = a || "default";   // Matches 0, "", null, undefined, NaN, false.
  a || (a = "default"); // Matches 0, "", null, undefined, NaN, false.

  if (b == null) { b = "default"; } // Matches null, undefined.

  if (typeof c === "undefined") { c = "default"; } // Matches undefined.
}

Фальши, используемые по умолчанию, с переменной a , например, широко используются в Backbone.js .


Я считаю, что это самый простой и понятный способ:

if (typeof myVariable === 'undefined') { myVariable = 'default'; }
//use myVariable here

Ответ Пола Диксона (по моему скромному мнению) менее читабельен, чем это, но это сводится к предпочтению.

Ответ insin намного более продвинут, но гораздо полезнее для больших функций!

EDIT 11/17/2013 9:33 вечера: Я создал пакет для Node.js, что упрощает «перегрузку» функций (методов), называемых parametric .


Во всех случаях, когда optionalArg является ложным, вы получите defaultValue.

function myFunc(requiredArg, optionalArg) {
    optionalArg = optionalArg || 'defaultValue';
    console.log(optionalArg);
    // Do stuff
}
myFunc(requiredArg);
myFunc(requiredArg, null);
myFunc(requiredArg, undefined);
myFunc(requiredArg, "");
myFunc(requiredArg, 0);
myFunc(requiredArg, false);

Все вышеперечисленное значение logvalvalue, потому что все из 6 являются ложными. В случае 4, 5, 6 вам может быть неинтересно устанавливать опциональный атрибут как defaultValue, но он устанавливает, поскольку они являются ложными.


Я предлагаю вам использовать ArgueJS следующим образом:

function myFunc(){
  arguments = __({requiredArg: undefined, optionalArg: [undefined: 'defaultValue'})

  //do stuff, using arguments.requiredArg and arguments.optionalArg
  //    to access your arguments

}

Вы также можете заменить undefinedтип аргумента, который вы ожидаете получить, например:

function myFunc(){
  arguments = __({requiredArg: Number, optionalArg: [String: 'defaultValue'})

  //do stuff, using arguments.requiredArg and arguments.optionalArg
  //    to access your arguments

}

function foo(requiredArg){
  if(arguments.length>1) var optionalArg = arguments[1];
}




arguments