A javascript design pattern for options with default values?

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

The above seems awfully verbose. What's a better way to handle argument options with default parameters?

39840 次浏览

这使用 JQuery 扩展,但是可以与您选择的库中的对象合并或 ES6中的 Object.sign 进行交换。

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

Edit: Now also in underscore or 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

In Javascript ES6 you can use 对象. 分配:

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

现在想想,我有点喜欢这个:

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
}

我想你正在寻找类似这样的东西(抱歉回复得太晚了) :

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

edit: apologies, grabbed this from some old code... You should make sure to use the hasOwnProperty() method to make sure you don't iterate over everything on function.prototype

Https://developer.mozilla.org/en-us/docs/javascript/reference/global_objects/object/hasownproperty

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

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

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

如果您需要在许多连续的函数中执行此操作,那么一种标准化过程并加速它的方法是:

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

(可以检查兼容性表 here)


最后,ECMAScript 6会给我们带来最好的方式来做到这一点: default parameters

不过,这需要几个月的时间才能在各种浏览器上得到广泛支持。

对象属性使用 ES2018传播语法:

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


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

Using ES6/ES2015 features, several more options are available.

使用解构分配:

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

没有依赖!

虽然 对象. 分配是合并默认选项的一种非常直接的方式,但它也有一些缺点:

  1. 如果你想用三元运算符设置条件选项-它甚至会覆盖 undefined值的默认值:

    const options = {
    logging: isProduction ? 'none' : undefined
    };
    const defaults = {
    logging: 'verbose'
    }
    Object.assign({}, defaults, options); // {logging: undefined} !
    
  2. if you provide incorrect option name - you will not be warned:

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

To cover these cases I've created tiny flat-options package.
It does not overwrite defaults for undefined values:

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

希望这个能帮上忙!

如果你有访问 ES6 with a stage 4 proposal(如与巴别塔) ,你可以完成这与传播和解构分配。

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

(latter example from David Walsh - @wprl's answer also mentions this)

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

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

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

在不使用外部库的情况下使用 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 }

有一种新的 javascript 语法可以轻松设置默认值,逻辑赋值操作符:

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

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

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

(可以说我们应该总是使用 ??=版本,但它也是相当新的)

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

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