We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
在项目中我们经常会遇到初始化数据的需求,比如创建一个100个元素的数组,让每个元素的初始值为0。
非常传统的一种方式就是创建好数组,然后用循环来赋初始值。
function initialize(list) { for(let i = 0; i < list.length; i++) { list[i] = 0; } return list; } const list = new Array(100); initialize(list); console.log(list); // [0, 0, ... 0];
有同学可能想用数组的迭代方法来赋值,比如:
list.forEach((_, i) => { list[i] = 0; }); console.log(list); // [empty x 100]
或者:
const list = Array(100).map(() => 0); console.log(list); // [empty x 100]
但是事实上这两种方式都是不行的,因为数组被创建的时候,元素的初始值是empty,而迭代方法并不会遍历数组中empty的元素,for...in也一样。
empty
for...in
这个规则对于访问稀疏数组中的元素是有帮助的,能提升效率,但不在这篇文章讨论的范围,后续有机会我们会对数组迭代进行单独讨论,有兴趣的同学可以关注。
回到主题,虽然直接创建数组时数组元素的默认值是empty,但是因为数组是一个可迭代对象,所以我们可以用spread操作将它展开,再进行迭代就可以了,所以:
const list = [...Array(100)].map(() => 0); console.log(list); // [0, 0, ...0]
此外,我们还可以通过Array.from方法创建初始数组。
Array.from
👉🏻 Array.from方法从一个类数组或可迭代对象中创建一个新的、浅拷贝的数组实例。
一个包含length属性,且值为非负整数的对象会被当做类数组对象,被Array.from处理成一个长度为length的数组。
👉🏻Array.from 对象的第二个参数是迭代算子(Map Functor),所以我们不必用Array.from + map转两次,直接一次转换就可以了:
Array.from + map
const list = Array.from({length: 100}, () => 0); console.log(list); // [0, 0, ...0]
由于我们例子中初始化数组的初始值是固定的数值0,所以这里我们其实可以使用Array.prototype.fill方法。
Array.prototype.fill
const list = Array(100).fill(0); console.log(list); // [0, 0, ...0]
💡注意fill方法用来填充元素的值如果是引用类型,它并不会拷贝这个值,而是直接将引用赋给数组,这也就是说,如果我们企图通过fill来初始化二维数组,是有问题的。
const matrix = Array(3).fill(Array(3).fill(0)); matrix[0][1] = 1; console.log(matrix); // [[0, 1, 0], [0, 1, 0], [0, 1, 0]]
上面的代码,matrix[0]、matrix[1]和matrix[2]指向同一个引用。
另外,fill还有两个可选的参数,可以对数组进行部分赋值:
const list = Array(10); list.fill(0, 0, 5); list.fill(1, 5); console.log(list); // [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
所以对于初始化相同的非引用类型值的数组,使用Array.prototype.fill是比较简单的方案。
但是,如果我们初始化的值不同呢?
比如我们想初始化一个长度10的数组,初始值分别是0~9。
通过前面的Array.from是一个简单的办法:
const list = Array.from({length: 10}, (_, i) => i); console.log(list); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
就这个问题还有个取巧的办法,因为我们要初始化的值恰好是数组元素的下标值,所以:
const list = [...Array(10).keys()]; console.log(list);
👉🏻Array.protoype.keys返回一个以数组下标为迭代值的可迭代对象,将它展开就是我们要的结果了。
Array.protoype.keys
对于更复杂的初始化需求,我们可以构建可复用的生成器。
function *initializer(count, mapFunc = i => i) { for(let i = 0; i < count; i++) { yield mapFunc(i, count); } } const list1 = [...initializer(10)]; console.log(list1); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] const list2 = [...initializer(10, i => 10 + 2 * i)]; console.log(list2); // [10, 12, 14, 16, 18, 20, 22, 24, 26, 28]
在这里,有同学可能会说,我们不用生成器,直接将数组构建出来也是可以的:
function initialize(count, mapFunc = i => i) { const ret = []; for(let i = 0; i < count; i++) { ret.push(mapFunc(i, count)); } return ret; } const list1 = initialize(10); console.log(list1); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] const list2 = initialize(10, i => 10 + 2 * i); console.log(list2); // [10, 12, 14, 16, 18, 20, 22, 24, 26, 28]
这个的确是可以的,不过用生成器将构建迭代器和生成数组的步骤分开,会更灵活。
我们稍微修改一下生成器,构建更复杂的数据:
function *initializer(count, mapFunc = i => i) { for(let i = 0; i < count; i++) { const value = mapFunc(i, count); if(value[Symbol.iterator]) yield* value; else yield value; } } const mat3 = [...initializer(3, i => initializer(3, j => i === j ? 1 : 0))] console.log(mat3); // [1, 0, 0, 0, 1, 0, 0, 0, 1]
👉🏻yield* 可以将一个可迭代对象委托给一个生成器,所以上面的代码判断如果value返回的是一个可迭代对象,那么递归迭代这个对象并返回,所以我们用它来生成一个3x3的初始矩阵,它是一个长度为9的数组,初始值是[1, 0, 0, 0, 1, 0, 0, 0, 1]。
[1, 0, 0, 0, 1, 0, 0, 0, 1]
上面的代码有一个问题,就是它会将所有可迭代对象继续展开,这也许不是我们期望的结果,比如:
function *initializer(count, mapFunc = i => i) { for(let i = 0; i < count; i++) { const value = mapFunc(i, count); if(value[Symbol.iterator]) yield* value; else yield value; } } const list = [...initializer(3, i => [0, 0, 0])]; console.log(list); // [0, 0, 0, 0, 0, 0, 0, 0, 0]
可能我们的预期是初始化成[[0, 0, 0], [0, 0, 0], [0, 0, 0]],而不是完全展开成[0, 0, 0, 0, 0, 0, 0, 0, 0]。
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[0, 0, 0, 0, 0, 0, 0, 0, 0]
当然我们可以把调用代码修改一下,嵌套一层数组:
function *initializer(count, mapFunc = i => i) { for(let i = 0; i < count; i++) { const value = mapFunc(i, count); if(value[Symbol.iterator]) yield* value; else yield value; } } const list = [...initializer(3, i => [[0, 0, 0]])]; console.log(list); // [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
但是这很容易给使用者造成困扰。因此我们可以修改一下设计,只有mapFunc是生成器函数的时候,才用yield*迭代展开,否则直接yield返回。
yield*
yield
function *initializer(count, mapFunc = i => i) { for(let i = 0; i < count; i++) { if(mapFunc.constructor.name === 'GeneratorFunction') { yield* mapFunc(i, count); } else { yield mapFunc(i, count); } } } const list = [...initializer(3, i => [0, 0, 0])]; console.log(list); // [[0, 0, 0], [0, 0, 0], [0, 0, 0]] const mat3 = [...initializer(3, function *(i) { yield Number(i === 0); yield Number(i === 1); yield Number(i === 2); })] console.log(mat3); // [1, 0, 0, 0, 1, 0, 0, 0, 1]
最后,再强调一下,生成器是个好东西,用它来写简洁易读的代码来初始化数据吧,比如下面的代码初始化一副扑克牌:[花色, 点数](不包括大小王):
[花色, 点数]
function *initializer(count, mapFunc = i => i) { for(let i = 0; i < count; i++) { if(mapFunc.constructor.name === 'GeneratorFunction') { yield* mapFunc(i, count); } else { yield mapFunc(i, count); } } } const cards = [...initializer(13, function *(i) { const p = i + 1; yield ['♠️', p]; yield ['♣️', p]; yield ['♥️', p]; yield ['♦️', p]; })]; console.log(cards);
关于生成结构化的初始数据,你还有什么想要讨论的,欢迎在issue中讨论。
The text was updated successfully, but these errors were encountered:
感觉上生成扑克牌其实用generator还是大材小用了。
可能这样就够了:
const cards = [] for (const suit of ['♠️','♣️','♥️','♦️']) for (const point of '123456789TJQK') cards.push([suit, point])
或者
const cards = ['♠️','♣️','♥️','♦️'].flatMap(suit => [...'123456789TJQK'].map(point => [suit, point]))
Sorry, something went wrong.
一种函数组合风格
const pipe = (...args) => args.reduce((a, f) => f(a)) const bind = f => list => [...list.map(f)] const flat = list => list.flat(1) const pair = list => a => pipe(list, bind(b => [a, b])) const cards = pipe(['♠️', '♣️', '♥️', '♦️'], bind(pair(Array.from('123456789TJQK'))), flat)
No branches or pull requests
在项目中我们经常会遇到初始化数据的需求,比如创建一个100个元素的数组,让每个元素的初始值为0。
非常传统的一种方式就是创建好数组,然后用循环来赋初始值。
有同学可能想用数组的迭代方法来赋值,比如:
或者:
但是事实上这两种方式都是不行的,因为数组被创建的时候,元素的初始值是
empty
,而迭代方法并不会遍历数组中empty的元素,for...in
也一样。这个规则对于访问稀疏数组中的元素是有帮助的,能提升效率,但不在这篇文章讨论的范围,后续有机会我们会对数组迭代进行单独讨论,有兴趣的同学可以关注。
回到主题,虽然直接创建数组时数组元素的默认值是empty,但是因为数组是一个可迭代对象,所以我们可以用spread操作将它展开,再进行迭代就可以了,所以:
此外,我们还可以通过
Array.from
方法创建初始数组。👉🏻 Array.from方法从一个类数组或可迭代对象中创建一个新的、浅拷贝的数组实例。
一个包含length属性,且值为非负整数的对象会被当做类数组对象,被
Array.from
处理成一个长度为length的数组。👉🏻Array.from 对象的第二个参数是迭代算子(Map Functor),所以我们不必用
Array.from + map
转两次,直接一次转换就可以了:由于我们例子中初始化数组的初始值是固定的数值0,所以这里我们其实可以使用
Array.prototype.fill
方法。💡注意fill方法用来填充元素的值如果是引用类型,它并不会拷贝这个值,而是直接将引用赋给数组,这也就是说,如果我们企图通过fill来初始化二维数组,是有问题的。
上面的代码,matrix[0]、matrix[1]和matrix[2]指向同一个引用。
另外,fill还有两个可选的参数,可以对数组进行部分赋值:
所以对于初始化相同的非引用类型值的数组,使用
Array.prototype.fill
是比较简单的方案。但是,如果我们初始化的值不同呢?
比如我们想初始化一个长度10的数组,初始值分别是0~9。
通过前面的
Array.from
是一个简单的办法:就这个问题还有个取巧的办法,因为我们要初始化的值恰好是数组元素的下标值,所以:
👉🏻
Array.protoype.keys
返回一个以数组下标为迭代值的可迭代对象,将它展开就是我们要的结果了。使用生成器
对于更复杂的初始化需求,我们可以构建可复用的生成器。
在这里,有同学可能会说,我们不用生成器,直接将数组构建出来也是可以的:
这个的确是可以的,不过用生成器将构建迭代器和生成数组的步骤分开,会更灵活。
我们稍微修改一下生成器,构建更复杂的数据:
👉🏻yield* 可以将一个可迭代对象委托给一个生成器,所以上面的代码判断如果value返回的是一个可迭代对象,那么递归迭代这个对象并返回,所以我们用它来生成一个3x3的初始矩阵,它是一个长度为9的数组,初始值是
[1, 0, 0, 0, 1, 0, 0, 0, 1]
。上面的代码有一个问题,就是它会将所有可迭代对象继续展开,这也许不是我们期望的结果,比如:
可能我们的预期是初始化成
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
,而不是完全展开成[0, 0, 0, 0, 0, 0, 0, 0, 0]
。当然我们可以把调用代码修改一下,嵌套一层数组:
但是这很容易给使用者造成困扰。因此我们可以修改一下设计,只有mapFunc是生成器函数的时候,才用
yield*
迭代展开,否则直接yield
返回。最后,再强调一下,生成器是个好东西,用它来写简洁易读的代码来初始化数据吧,比如下面的代码初始化一副扑克牌:
[花色, 点数]
(不包括大小王):关于生成结构化的初始数据,你还有什么想要讨论的,欢迎在issue中讨论。
The text was updated successfully, but these errors were encountered: