Underscore: 非数组值的数组链方法错误

创建于 2016-03-20  ·  6评论  ·  资料来源: jashkenas/underscore

数组链方法在非数组值上出错,这与其他数组类别方法不同。

_().pop(); // error
_('').pop(); // error
_().first() // undefined

最有用的评论

这带来了另一个问题,我们应该将array-likes 转换为array 吗?

_('hello').first() // 'h'
_('hello').pop() // ?

所有6条评论

这带来了另一个问题,我们应该将array-likes 转换为array 吗?

_('hello').first() // 'h'
_('hello').pop() // ?

这似乎仍然是一个问题 - 我们在将我们的版本从 1.8.3 t0 1.9.0 升级后遇到了它

编辑:看起来这在 1.9.1 中已修复,如果是这样,这个问题是否需要保持开放?

这种行为在 1.10.2 中仍然存在。

在我的选择中,一个稍微更有说服力的对比,因为两者都涉及修改一个适当的值:

Object.assign(undefined, {a: 1})  // error
_().extend({a: 1})  // undefined

Array.prototype.push.call(undefined, 1)  // error
_().push(1)  // error

我有点同意这是不一致的。

值得考虑的是在什么样的现实世界情况下最有可能遇到这种情况,以及在这种情况下包装对象会发生什么。

在链的中间,被包裹的对象是可预测的。 例如,在下面的链中,传递给push的包装对象将是Arrayundefined 。 出于这个原因,我认为Array包装器方法应该实现空检查。 这很简单。

_.chain(something).map(f).push(x)  // hoping to push x to an array

如果预测的包装对象更可能是数组以外的东西,那么它可以说是程序员错误。 我不介意在这种情况下抛出异常。

_.chain(something).map(f).join('').push(x)  // hoping to push x to a string??

在链的开始,乍一看,故事可能看起来有点不同,但我会认为它是相同的,我们应该将其保留为空检查。 至少程序员一定有理由期望something是一个可变数组,否则就没有理由调用push

function giveMeAMutableArraylike(something) {
    _.chain(something).push(x)
}

something是一个类似可变数组的期望可能由于各种原因而无法满足:

  1. giveMeAMutableArraylike最终以nullundefined调用,无论出于何种原因。 这与第一个中间链示例相当,可以使用相同的空检查来解决。
  2. 调用者试图让giveMeAMutableArraylike做一些它显然不能做的事情,即违反合同。 这与第二个中间链示例相当。 同样,我认为在这种情况下抛出错误是可以的。
  3. something是一个裸值而不是具有单个元素的数组,即v而不是[v] 。 这是许多 JavaScript API 中的常见情况。 如果giveMeAMutableArraylike的合约允许这样做,那么无论v是什么,抛出错误显然是错误的。

在后一种情况下,Underscore 无法知道giveMeAMutableArraylike是否允许单个裸元素,因此由giveMeAMutableArraylike的程序员来实现其特定的合约。 像 Underscore 这样的库无法为所有可能的合约做正确的事情:

| 当前行为 | 包裹在单元素数组中
---|---|---
合约允许裸单元素| _程序员需要干预_ | 下划线自动做正确的事
合约不允许裸单元素| 下划线自动做正确的事 | _程序员需要干预_

另外,你如何决定一个值是否应该被包装? 一些合约可能想要包装任何不是Array的东西,而其他合约可能想要按原样传递arguments和普通对象。 同样,这是像 Underscore 这样的库无法代表用户猜测的东西。

更奇特的情况也是可以想象的,例如允许不可变的类似数组的对象(如字符串)的合约,并且首先将它们转换为Array 。 不可能涵盖所有可能的变化。 根据最小变化原则,以牺牲已经支持的合同为代价来支持某些合同并没有令人信服的论据。 还有一个强有力的理由来尽可能地减少实现。

所以我认为正确的解决方案是只插入一个空检查并保留它。 这是对 Underscore 行为的唯一更改,在所有可能的情况下似乎都是合理的,并且也与_.extend的行为一致。 空检查仍然可以被视为错误修复,而做更多的事情会很快将其变成任意的破坏性更改。

@jashkenas您能对此有所了解吗? 如果您同意,我将准备一个实现 null 检查的拉取请求。

@jgonggrijp — 你能用一两句话详细说明你想用空检查实现的行为吗?

是不是数组方法在调用空值时会显式抛出异常? 或者当调用一个空值时他们会打盹?

@jashkenas是的。 我将实现的行为是无操作,遵循下划线数组函数的样式:

https://github.com/jashkenas/underscore/blob/4cf715f593805ba8d7c5685cd06c82b3cd9b55ae/modules/index.js#L495

除了不需要length检查,所以我只需插入

if (obj == null) return chainresult(this, obj);

在这两行之间:

https://github.com/jashkenas/underscore/blob/4cf715f593805ba8d7c5685cd06c82b3cd9b55ae/modules/index.js#L1654 -L1655

当然还有测试,如果这些测试显示需要,也许还有一个空检查,将方法变成此行之前的无操作:

https://github.com/jashkenas/underscore/blob/4cf715f593805ba8d7c5685cd06c82b3cd9b55ae/modules/index.js#L1665

对我来说听起来不错!

此页面是否有帮助?
0 / 5 - 0 等级