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


71
// 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和下划线都带有一个extend方法,可以很好地处理此问题。
笨蛋2012年

1
@blockhead-因此,使用extend并且不保留选项的局部变量?
ripper234'3

1
实际上,过了一会儿,我得出结论,说到Javascript命名参数,冗长是一件好事。在函数开始时将每个参数复制到变量是记录所需选项的好方法,并且还使您可以修改它们而不必与原始对象拧在一起。
hugomg 2012年

@missingno-我倾向于同意,但是我不喜欢每个变量出现两次。在下面查看我的解决方案。
ripper234'3

Answers:


79

这使用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

编辑:现在也underscore还是lodash

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,而是想要实际的选择。
ripper234 2012年

我做了一个较小的样式编辑,更改'color'color-我认为这是更惯用的js,不是吗?
ripper234 2012年

编辑了命名,使其更明显。
最大

2
聚会晚了,但是object.assign的问题在于它将复制“ options”中的整个对象。因此,如果您有一个深层的嵌套结构,它将不会合并那些选项,它将覆盖它们。
拉里·布德(LarryBud)

@LarryBud多数民众赞成在一个好点。不过,深层嵌套似乎超出了原始问题的范围。
最多

45

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

没有依赖!


6
这种模式很棒,为我节省了很多房地产。尽管应该指出的是,任务是肤浅的,而不是深度的
迈步机

20

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

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

3
另外,我想指出,截至10月22日,ripper234接受的答案无法正常运行。在代码示例中,即使options对象中存在其他值,opt_c和opt_d始终具有default_for_c和default_for_d。将参数的顺序更改为逻辑OR可以解决此问题(a || b vs b || a)。
瑞克·戴卡德

11

还有更紧凑的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"} 

1
缺点是您的代码现在依赖jQuery进行操作,如果这是使用jQuery的唯一原因。您将通过包含jQuery在客户端添加许多不必要的代码。
CoasterChris

2

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

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

这样,您只需在函数内部定义一个选项对象,其中包含默认值。

如果要进行额外检查以避免原型继承,则第一个函数可以是:

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

IE <9,Chrome <5,Firefox <4,Safari <5

(您可以在此处查看兼容性表)


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

但是要花几个月的时间才能在各种浏览器中广泛支持。


9
“尽管要在各种浏览器中得到广泛支持,否则还需要几个月的时间。” 我喜欢你的乐观。:)
最高

2

在不使用外部库的情况下使用ES6 Spread Operator

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 }

1

如果您可以通过第4阶段的提案(例如Babel)访问ES6,则可以通过扩展和解构分配来完成。

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的答案也提到了这一点)


1
      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;
    }

1

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

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

0

尽管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."

希望这可以帮助!


0

有一个新的JavaScript语法,可轻松设置默认值,逻辑赋值运算符:

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

如果您希望使用更严格的布尔语义,也可以使用null运算符:

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

(可以说我们应该一直使用??=版本,但是它也是很新的)

另外,我一直都使用默认参数,但是此语法可在任何解构分配中使用:

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

-1

我认为您正在寻找这样的东西(对不起,您的答复很晚):

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/zh-CN/docs/JavaScript/Reference/Global_Objects/Object/hasOwnProperty


这里有一个错别字:您正在检查选项是否为字符串"undefined"而不是真正未定义的字符串(您可能省略了typeof,但是options[i] !== undefined如果您愿意假设某些bozo没有重新定义undefined)也很好。
Matt Kantor 2014年

有关此伪代码的有效版本,请参见Rick Deckard的答案
ChristianDavén2014年

-6

现在,考虑一下,我有点像这样:

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
}

继续我们的讨论...我也曾经这样想过,因为我是一个顽固的代码重复讨厌者,但是我改变了主意。我意识到,我经常会忘记接受哪些选项(并且必须搜索功能代码以找出答案)。然后,II开始添加注释以列出命名的参数,然后我继续复制所有变量,对所有变量进行相同的处理。尽管键入时间稍长,但是它不会遭受注释腐烂的影响,并且更加灵活(我可以在函数内部重命名变量,可以轻松添加和删除默认值,等等。)。
hugomg

@missingno-我不确定我是否遵循。在这个答案中,我确实将变量复制到变量中,并且不会遭受代码腐烂的困扰。我只是不重复两次
ripper234 2012年

4
如果不是这样,var opt_c = opt_options.c || 'default_for_c';似乎opt_options.c不会实现,因为'default_for_c'它将评估为真实。
Sukima 2014年

2
@Sukima另一个问题是,如果将opt_options.c设置为false,然后将其更改回true,这是不希望的。在这种情况下,最好显式检查未定义。
Ray Suelzer

1
您需要使用undefined,因为必须在javascript中将null值显式声明为null。
Ray Suelzer
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.