具有默认值的选项的javascript设计模式?

Posted

技术标签:

【中文标题】具有默认值的选项的javascript设计模式?【英文标题】:A javascript design pattern for options with default values? 【发布时间】: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 - 我倾向于同意,但我不喜欢每个变量出现两次。在下面查看我的解决方案。 【参考方案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 是将选项与默认值合并的直接方法,但它有一些缺点:

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

    const options = 
      logging: isProduction ? 'none' : undefined
    ;
    const defaults = 
      logging: 'verbose'
    
    Object.assign(, defaults, options); // logging: undefined !
    

    如果您提供不正确的选项名称 - 您不会收到警告:

    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。

以上是关于具有默认值的选项的javascript设计模式?的主要内容,如果未能解决你的问题,请参考以下文章

Javascript将具有多个值的选定选项从一个列表移动到另一个列表

如何在具有 null 值的 Angular 选择元素上使用默认的“请选择”选项进行验证?

SQL - 选择具有默认值的期间的数据

显示表单字段默认值的最佳可访问方式

如何在Javascript中将选定选项设置为默认选择

选择具有 2 个空选项值的输入