【问题标题】:A javascript design pattern for options with default values?具有默认值的选项的javascript设计模式?
【发布时间】:2012-03-25 00:54:58
【问题描述】:
// opt_options is optional
function foo(a, b, opt_options) {
  // opt_c, opt_d, and opt_e are read from 'opt_options', only c and d have defaults
  var opt_c = 'default_for_c';
  var opt_d = 'default_for_d';
  var opt_e; // e has no default

  if (opt_options) {
    opt_c = opt_options.c || opt_c;
    opt_d = opt_options.d || opt_d;
    opt_e = opt_options.e;
  }
}

上面的内容看起来非常冗长。使用默认参数处理参数选项的更好方法是什么?

【问题讨论】:

  • jQuery 和 underscore 都带有一个可以很好地处理这个问题的 extend 方法。
  • @blockhead - 所以,使用extend 并且不要为选项保留局部变量?
  • 实际上,一段时间后我得出结论,当涉及到 Javascript 命名参数时,冗长是一件好事。在函数开始时将每个参数复制到变量是记录预期选项的好方法,并且还可以让您修改它们而不会与原始对象搞砸。
  • @missingno - 我倾向于同意,但我不喜欢每个变量出现两次。在下面查看我的解决方案。

标签: javascript design-patterns option optional-parameters


【解决方案1】:

将 ES2018 spread syntax 用于对象属性:

const defaults = { a: 1, b: 2 };

const ƒ = (given = {}) => {
  const options = { ...defaults, ...given };
  console.log(options);
};

使用 ES6/ES2015 功能,有更多选项可用。

使用解构赋值:

const { a = 1, b = 2 } = options;

你也可以使用解构函数参数:

const ƒ = ({a = 1, b = 2, c = 3} = {}) => {
   console.log({ a, b, c });
};

使用Object.assign

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

没有依赖关系!

【讨论】:

  • 这种模式很棒,为我节省了很多房地产。虽然应该注意的是,分配是浅的,而不是深的
  • 小心!如果您的options 输入具有映射到undefined 的已知键(例如a),您将不会从defaults 中获取该属性的值
【解决方案2】:

有一种新的 javascript 语法可以轻松设置默认值、逻辑赋值运算符:

// Super crazy this:
staticConfig.defaultPropsForFoo = 
  staticConfig.defaultPropsForFoo || {
    myDefault: 'props'
  }
// turns into:
staticConfig.defaultPropsForFoo ||= { myDefault: 'props' }

如果您更喜欢更严格的布尔语义,也可以使用 nullish 运算符:

staticConfig.defaultPropsForFoo ??= { myDefault: 'props' }

(可以说我们应该始终使用??= 版本,但它也很新)

另外,我一直使用默认参数,但是这种语法在任何解构赋值中都有效:

const {
  defaultPropsForFoo = { myDefault: 'props' },
  ...restConfig
} = staticConfig

【讨论】:

    【解决方案3】:

    使用 ES6 扩展运算符而不使用外部库

    function Example(opts) {
       let defaults = { foo: 1, bar: 2 }
       opts = { ...defaults, ...(opts || {}) }
       console.log(opts);
    }
    
    Example({ bar: 3, baz: 4 })
    
    // { foo: 1, bar: 3, baz: 4 }
    

    【讨论】:

      【解决方案4】:

      这是一个简单而干净的方法,希望对某人有所帮助:

      function example(url, {title = false, check = false, wait = false} = {}){
        console.log('url: ', URL);
        console.log('title: ', title);
        console.log('check: ', check);
        console.log('wait: ', wait);
      }
      
      example('https://example.com', {wait: 20})
      

      这是上面代码的输出:

      url:  https://example.com
      title:  false
      check:  false
      wait:  20
      

      【讨论】:

        【解决方案5】:

        这使用 jQuery.extend,但可以与您选择的库中的对象合并或 ES6 中的 Object.assign 互换。

        function Module(options){
            var defaults = {
                color: 'red'
            };
            var actual = $.extend({}, defaults, options || {});
            console.info( actual.color );
        }
        
        var a = new Module();
        // Red
        var b = new Module( { color: 'blue' } );
        // Blue
        

        编辑:现在也在underscorelodash

        function Module(options){
            var actual = _.defaults(options || {}, {
                 color: 'red'
            });
            console.info( actual.color );
        }
        
        var a = new Module();
        // Red
        var b = new Module( { color: 'blue' } );
        // Blue
        

        在 Javascript ES6 中你可以使用Object.assign:

        function Module(options = {}){
            let defaults = {
                color: 'red'
            };
            let actual = Object.assign({}, defaults, options);
            console.info( actual.color );
        }
        

        【讨论】:

        • 命名有点令人困惑......你不是真的想要defaults.color,你想要实际的选项。
        • 我做了一个小的样式编辑,将'color' 更改为color - 我认为这是更惯用的 js,不是吗?
        • 编辑了命名,使其更加明显。
        • 迟到了,但是 object.assign 的问题是它会从“options”复制整个对象。所以如果你有一个深层嵌套结构,它不会合并这些选项,它会覆盖它们。
        • @LarryBud 说得好。不过,深层嵌套似乎有点超出了原始问题的范围。
        【解决方案6】:
              var mergeOptions = function mergeOptions(userOptions) {
        
                // Default options
                var options = {
                    height: "100px",
                    width: "100px" ,
                    color: "blue"
                }
        
                if (userOptions) {
                    Object.keys(userOptions).forEach(function (key) {
                        options[key] = userOptions[key]
                    })
                }
        
                return options;
            }
        

        【讨论】:

          【解决方案7】:

          如果您有权访问ES6 with a stage 4 proposal(例如使用 Babel),您可以通过展开和解构赋值来完成此操作。

          const defaultPrintOptions = {
            fontName: "times",
            fontStyle: "normal",
            fontSize: 10,
            align: "left"
          };
          
          // Setting the null default isn't necessary but
          // makes it clear that the parameter is optional.
          // Could use {} but would create a new object
          // each time the function is called.
          function print(text, options = null) {
            let {
              fontName,
              fontStyle,
              fontSize,
              align
            } = {
              ...defaultPrintOptions,
              ...options
            };
          
            console.log(text, fontName, fontStyle, fontSize, align);
          }
          
          print("All defaults:");
          print("Override some:", {
            fontStyle: "italic",
            align: "center"
          });
          print("Override all:", {
            fontName: "courier",
            fontStyle: "italic",
            fontSize: 24,
            align: "right"
          });

          这也有效(但可能会创建更多对象):

          function myFunction({ 
            text = "", 
            line = 0, 
            truncate = 100 
          } = {}) {
            console.log(text, line, truncate);
          }
          

          (来自David Walsh 的后一个示例-@wprl 的回答也提到了这一点)

          【讨论】:

            【解决方案8】:

            虽然Object.assign 是将选项与默认值合并的直接方法,但它有一些缺点:

            1. 如果您想使用三元运算符设置条件选项 - 即使undefined 值也会覆盖默认值:

              const options = {
                logging: isProduction ? 'none' : undefined
              };
              const defaults = {
                logging: 'verbose'
              }
              Object.assign({}, defaults, options); // {logging: undefined} !
              
            2. 如果您提供不正确的选项名称 - 您不会收到警告:

              const options = {
                loging: 'none' // typo
              };
              const defaults = {
                logging: 'verbose'
              }
              Object.assign({}, defaults, options); // {logging: 'verbose', loging: 'none'} !
              

            为了解决这些情况,我创建了微小的 flat-options 包。
            它不会覆盖 undefined 值的默认值:

            const options = {
              logging: isProduction ? 'none' : undefined
            };
            const defaults = {
              logging: 'verbose'
            }
            flatOptions(options, defaults); // {logging: 'verbose'}
            

            并警告选项名称不正确:

            const options = {
              loging: 'none' // typo
            };
            const defaults = {
              logging: 'verbose'
            }
            flatOptions(options, defaults); // throws "Unknown option: loging."
            

            希望这会有所帮助!

            【讨论】:

              【解决方案9】:

              如果您需要在许多连续的功能中执行此操作,那么标准化流程并加快流程的方法是:

              function setOpts (standard, user) {
                if (typeof user === 'object' {
                  for (var key in user) {
                    standard[key] = user[key];
                  }
                }
              }
              

              然后你可以像这样简单地定义你的函数:

              var example = function (options) {
                var opts = {
                  a: 1,
                  b: 2,
                  c:3
                };
                setOpts(opts, options);
              }
              

              这样你只在函数内部定义一个选项对象,它包含默认值。

              如果要对avoid prototype inheritance进行额外检查,第一个函数可以是:

              function setOpts (standard, user) {
                if (typeof user === 'object') {
                  Object.keys(user).forEach(function (key) {
                    standard[key] = user[key];
                  });
                }
              }
              

              不支持后一种情况:IE

              (可以查看兼容性表here


              最后,ECMAScript 6 将为我们带来最好的方法:default parameters

              这需要几个月的时间才能在浏览器中得到广泛支持。

              【讨论】:

              • "这需要几个月的时间才能在浏览器中得到广泛支持。"。我喜欢你的乐观。 :)
              【解决方案10】:

              还有更紧凑的 jQuery 版本:

              function func(opts) {
                  opts = $.extend({
                      a: 1,
                      b: 2
                  }, opts);
              
                  console.log(opts);
              }
              
              func();            // Object {a: 1, b: 2} 
              func({b: 'new'});  // Object {a: 1, b: "new"} 
              

              【讨论】:

              • 缺陷在于你的代码现在依赖 jQuery 来运行,如果这是使用 jQuery 的唯一原因。通过包含 jQuery,您在客户端添加了许多不必要的代码。
              【解决方案11】:

              为了获得没有额外依赖的默认选项,我使用以下模式:

              var my_function = function (arg1, arg2, options) {
                  options = options || {};
                  options.opt_a = options.hasOwnProperty('opt_a') ? options.opt_a : 'default_opt_a';
                  options.opt_b = options.hasOwnProperty('opt_b') ? options.opt_b : 'default_opt_b';
                  options.opt_c = options.hasOwnProperty('opt_c') ? options.opt_c : 'default_opt_b';
              
              
                  // perform operation using options.opt_a, options.opt_b, etc.
              };
              

              虽然有点冗长,但我发现它易于阅读、添加/删除选项和添加默认值。当有很多选项时,稍微紧凑的版本是:

              var my_function = function (arg1, arg2, options) {
                  var default_options = {
                      opt_a: 'default_opt_a',
                      opt_b: 'default_opt_b',
                      opt_c: 'default_opt_c'};
              
                  options = options || {};
                  for (var opt in default_options)
                      if (default_options.hasOwnProperty(opt) && !options.hasOwnProperty(opt))
                          options[opt] = default_options[opt];
              
                  // perform operation using options.opt_a, options.opt_b, etc.
              };
              

              【讨论】:

              • 另外,我想指出,截至 10 月 22 日,ripper234 接受的答案无法正常工作。在代码示例中 opt_c 和 opt_d 始终具有 default_for_c 和 default_for_d,即使选项对象中存在其他值。将参数的顺序更改为逻辑 OR 可以解决此问题(a || b 与 b || a)。
              【解决方案12】:

              我认为您正在寻找这样的东西(抱歉回复晚了):

              function foo(a, b, options) { 
                  this.defaults = {
                      x: 48, 
                      y: 72,
                      z: 35
                  };
                  for (var i in this.defaults) {
                      if (options[i] != "undefined") { this.defaults[i] = options[i]; }
                  }
                  // more code...
              }
              

              编辑:抱歉,这是从一些旧代码中获取的...您应该确保使用 hasOwnProperty() 方法来确保您不会遍历 function.prototype 上的所有内容

              https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/hasOwnProperty

              【讨论】:

              • 这里有一个错字:您正在检查选项是否是字符串"undefined",而不是真正的未定义(您可能遗漏了typeof,但options[i] !== undefined 也很好,如果您'我愿意假设某些笨蛋没有重新定义undefined)。
              • 有关此伪代码的工作版本,请参阅the answer by Rick Deckard
              【解决方案13】:

              现在回想起来,我有点喜欢这样:

              function foo(a, b, opt_options) {
                // Force opt_options to be an object
                opt_options = opt_options || {};
              
                // opt_c, opt_d, and opt_e are read from 'opt_options', only c and d have defaults
                var opt_c = 'default_for_c' || opt_options.c;
                var opt_d = 'default_for_d' || opt_options.d;
                var opt_e = opt_options.e; // e has no default
              }
              

              【讨论】:

              • 继续我们的讨论......我以前也这样想,因为我是一个顽固的代码重复仇恨者,但我改变了主意。我意识到我经常会忘记接受哪些选项(并且必须搜索功能代码才能找到)。然后我开始添加 cmets 以列出命名参数,然后我继续复制所有变量,将它们全部视为相同。虽然输入的时间有点长,但它不会受到注释腐烂的影响,而且更灵活(我可以重命名函数内的变量,我可以轻松地添加和删除默认值等等......)。跨度>
              • @missingno - 我不确定我是否遵循。在这个答案中,我确实将变量复制到变量中,这不会受到代码腐烂的影响。我只是不重复它们两次
              • @Sukima 另一个问题是,如果 opt_options.c 设置为 false,它会被改回 true,这不是有意的。在这种情况下,明确检查 undefined 可能会更好。
              • 您可能想使用 undefined,因为 null 值必须在 javascript 中显式声明为 null。
              猜你喜欢
              • 1970-01-01
              • 2015-02-07
              • 2018-05-02
              • 2020-10-14
              • 1970-01-01
              • 2019-06-28
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多