业务背景
看他这个源码,感觉是个bug,但是这个库怎么会有bug呢,肯定是自己哪里搞错了,但实在是不知道错在哪儿
疑问
-
baseFlatten
函数的一个参数predicate
默认值是一个函数isFlattenable
-
isFlattenable
通过判断数组/类数组/Symbol.isConcatSpreadable来确定对象是否可以被解构...
但是
-
我查阅了MDN文档
Symbol.isConcatSpreadable
只会影响Arrany.prototype.conact方法 -
而
Symbol.iterator
才会影响到解构赋值
复现
var b = [2, 3];
console.log([].push(...b)); // => [2, 3];
b[Symbol.isConcatSpreadable] = false;
console.log([].push(...b)); // => [2, 3]
源代码
https://github.com/ranwawa/lo...
isFlattenable
import isArguments from '../isArguments.js';
const spreadableSymbol = Symbol.isConcatSpreadable;
function isFlattenable(value) {
return Array.isArray(value) || isArguments(
value) || !!(value && value[spreadableSymbol]);
}
baseFlatten
注意倒数第7行
// 深度合并数组的操作
function baseFlatten(array, depth, predicate, isStrict, result) {
predicate || (predicate = isFlattenable)
result || (result = [])
if (array == null) {
return result
}
for (const value of array) {
if (depth > 0 && predicate(value)) {
if (depth > 1) {
// Recursively flatten arrays (susceptible to call stack limits).
baseFlatten(value, depth - 1, predicate, isStrict, result)
} else {
// 即使value的Symbol.isConcatSpreadable为false,也可以被解构啊
result.push(...value)
}
} else if (!isStrict) {
result[result.length] = value
}
}
return result
}
问题
我是直接看的master分支,难道是因为我看的分支出错的原因?
###因为 isFlattenable
是给 flatten
族函数用的。而 flatten
的实现是按照 tc39/flat 提案实现的,在规范中 flat
中并没有使用 @@iterator
。
有一个非常简单的解释:因为字符串也是可迭代的。比如下面代码:
['hello', 'world'].flat()
如果输出的结果是 ['h', 'e', 'l', 'l', ...]
是不是很怪异。
因为 Array 的大多数方法的出现都比 @@iterator
要早,所以如果你去看数组函数,Array.from
可能是目前唯一一个是用了 @@iterator
的函数。
所以,这个函数没有 bug 吗?并不是。
这个函数的实现和规范并不兼容,但是问题不是在于 @@iterator
,而是在于 @@isConcatSpreadable
。根据规范,flat
函数是不检查 @@isConcatSpreadable
的。
官方的意见是,我们是按照 tc39/flat 提案实现的这个函数,但是后来提案变了。
如果 lodash 改变了这个函数,就会导致一个 break change。
如果你真想研究规范是如何用 js 实现的,推荐看看 core-js 源码,真是教科书式的源码。而 lodash 毕竟是第三方工具库,看 lodash 源码就先忽略规范,只关注他的奇技淫巧就够了。
###能解构不代表一定要展开。策略问题,默认仅展开数组拼接(concat)时会展开的那些元素。如果要把所有能展开的全部展开,可以自定义判断函数。