Underscore.js默认函数的无副作用版本?

3
为了使用默认值初始化对象,我使用Underscore的default函数。根据文档,它的工作原理如下:
var foo = _.defaults(object, *defaults)

这被描述为:

使用默认对象中的值填充对象中未定义的属性,并返回对象。一旦填充了属性,进一步的默认值将不起作用。

虽然它基本上运行良好,但我总是会遇到一个问题:操作原始对象的副作用。

如果我运行

var foo = { bar: 'baz' };

然后说

var bar = _.defaults(foo, { dummy: 23 });

不仅如此,bar 还拥有一个名为 dummy 的属性,原始的 foo 对象也被改变了。我的当前解决方法是:

var bar = _.defaults({}, foo, { dummy: 23 });

很遗憾,您很容易忘记这一点。我认为 defaults 函数既更改输入参数,又将结果作为返回值返回,这是相当奇怪的行为。应该只有一种方式。

你如何处理这种情况?有更好的方法来处理吗?


1
我完全同意你的观点。通常更改参数是一种反模式,因为它会产生非明显的副作用。我很惊讶很多框架(比如underscore和lodash)选择了这种行为。我认为它会诱导开发人员编写有缺陷的代码。 - Peter Dotchev
3个回答

4
该函数恰好按照其文档所述执行。修改传递到函数中的对象并将其返回并不罕见。返回是为了方便编写与“workaround”完全相同的代码。
您的“workaround”是正确的方法。
另一个选择是使foo成为新对象的原型:
var bar = _.defaults(Object.create(foo), { dummy: 23 });

......但我只知道通过查看Underscore的注释源代码(该链接可能会失效,但它会在附近)才能确定其工作方式,因为文档没有指定在复制默认值时,它是否仅查找“自有”属性。(实际上不是这样,它使用in。)

另外请注意,Object.create是ES5标准,旧版浏览器可能需要一个shim来支持。上面的用法是可供shim的(因为我没有使用 Object.create 的第二个参数)。


我刚刚重新阅读了您的标题:

Underscore.js默认函数的无副作用版本?

如果您不喜欢您的解决方法,则可以相对容易地创建一个无副作用版本:

function myDefaults() {
    var args = [{}];
    args.push.apply(args, arguments);
    return _.defaults.apply(_, args);
}

这将创建一个空白对象并将其放置在参数的开头,然后链接到_.defaults


1
不,这种行为完全符合“副作用”的定义,参见维基百科的定义:“在计算机科学中,如果一个函数或表达式除了返回一个值外,还修改了一些状态或与外部世界的调用函数有可观察的交互,则被称为具有副作用。” - c089
@c089:好的,术语方面没问题。 - T.J. Crowder
1
我的负评来自于最初的术语问题,很乐意撤回它。 - c089
@c089:我们应该清理这些注释。 - T.J. Crowder

1
我认为文档是正确的。
在构造函数中,您可以完全跳过使用返回值。像这样:
function(options) {
    _.defaults(options, defaults);
   // you can now continue to use options instead of a new object
}

如果那不是你想要的,我认为你的解决方法还不错。

1
我会简单地为它编写一个包装函数:

function defs (template_obj, additions) {
    return _.defaults({},template_obj,additions);
}

然后就像这样使用它:
var bar = defs(foo, { dummy: 23 });

你甚至可以将该功能添加到Underscore本身中(通过_.mixin函数,我相信),如果你愿意的话。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接