初始化空对象数组

初始化空对象数组

在给对象设置属性时, 如果对象不存在很容易报错.

property

有些场景, 在对对象数组处理时, 设置对象属性前判断对象是否存在. 与其这样, 还不如直接初始化为空对象数组.

9个考生就来了6个


考试时, 每个考生都有自己位置. 考生对照着可以很容易在考场里找到自己的座位.
秉着公平、公正、公开的原则, 考生被稀疏地散布在考场的各个角落.

假设考场 3*3 排列, 考生的信息:

1
2
3
4
5
6
[{"row":1,"col":1,"name":"Ada"},
{"row":3,"col":3,"name":"Aaron"},
{"row":1,"col":2,"name":"Aditi"},
{"row":3,"col":2,"name":"Aditi"},
{"row":1,"col":3,"name":"Aditi"},
{"row":3,"col":1,"name":"Abbott"}]

将考场位置做成一个表格, 对考生位置按排统计, 来标注考生出勤情况.

1
2
3
[{"row":1,"col_1":"Ada","col_2":"Aditi","col_3":"Aditi"},
{},
{"row":3,"col_3":"Aaron","col_2":"Aditi","col_1":"Abbott"}]

(为嘛没有第二排? 自知考不过, 缺考了呗🙁)
开发中, 对原始数据进行处理是一件很平常的事. so, 这个数据的处理应该很简单…吧😅

Array(3).fill({}) 试一波


如何初始化空对象数组?

原始数据是以学生个体的信息存储展示的, 现在则按排为单位对数据进行处理. 理所当然的会想到先初始化三个空对象数组.

1
2
3
let studentRow = Array(3).fill({})
// > studentRow
// [ {}, {}, {} ]

动作很快姿势很帅. 不过, 这样真的可以么? 长得倒是像那么一回事, 可实际上完全行不通. Array.prototype.fill() 的用法是, 指定某个值来填充数组.
也就是说, {} 在 studentRow 里复制了三次. 如果是简单类型值倒也罢了, 但是换做复杂类型值, 修改每一个 {} , 都会影响其它的 {}. 因为它们都是对同一个对象的引用.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let studentRow = Array(3).fill({});
studentRow[0].name = 'tony';
// > studentRow
// [ { name: 'tony' },
// { name: 'tony' },
// { name: 'tony' } ]

// 等同于
let obj = {};
let studentRow = Array(3).fill(obj);
// > studentRow
// {obj, obj, obj}
studentRow[0].name = 'tony';
// > studentRow
// [ { name: 'tony' },
// { name: 'tony' },
// { name: 'tony' } ]

知识点:

  • 将一个值赋予变量时, 解析器必须确定这个值是基本类型值还是复杂类型值.
  • 当是复杂类型值时, 变量里保存的是该复杂类型值在堆中的一个指针. 复制的是变量的指针, 操作的却是实际的对象.

Array(3)map(() => {}) 结合有问题


Array(3).fill({}) 行不通. 那么, Array(3).map(() => {})?

如果说 Array(3).fill({}) 不可行, 是因为三个空对象是对同一个对象的引用. 那么我们就设法返回三个不同的空对象.

1
2
3
let studentRow = Array(3).map(() => {});
// > studentRow
// [ <3 empty items> ]

结果很失望, 这个表达式就干了两件事, Array(3)map(() => {}). 所以问题很好排查.

1
2
3
let arr = Array(3);
// > arr
// [ <3 empty items> ]

对于数组中并不存在的单元, map() 也是束手无策.

我说: 肚里要有货🙏

肚里没货, 我们就造一些. Array.prototype.fill() 又有出头之日了.

1
2
3
let studentRow = Array(3).fill(undefined);
// > studentRow
// [ undefined, undefined, undefined ]

警告:

  • 如若一个数组没有任何单元, 但它的 length 属性中却显示有单元数量, 这样奇特的数据结构会导致一些怪异的行为. 我们将包含至少一个 “空单元” 的数组称之为 “稀疏数组”. undefined 单元非 “空单元”.

  • 永远不要创建和使用空单元数组.

箭头函数中的 return


你以为 Array(3).fill(undefined).map(() => {}) 就完事了? 图样图森破 👼

1
2
3
let studentRow = Array(3).fill(undefined).map(() => {});
// > studentRow
// [ undefined, undefined, undefined ]

哦, 我知道了, 你没有 return 啊

额, 这和 return 没有关系. 不信你可以加一个试试😏
其实, {} 在这里被视作语法块了, 没有任何意义. 可恨就可恨在, 它和空对象长得一摸一样.
既然这样, 那我们就不用字面量定义一个空对象了.

1
2
3
4
let studentRow = Array(3).fill(undefined).map(() => Object.create(null));
studentRow[0].name = 'tony';
// > studentRow
// [ { name: 'tony' }, {}, {} ]

这样就达到初始化对象数组的目的了. 可是, Array(3).fill(undefined).map(() => {}) 为什么行不通, 如何补救?

规避问题在某种意义上不等于解决问题.

{...} 里面的代码会被解析为一系列语句. {} 也因此不能达到我们预期的结果. 所以, 我们可以用 (...) 将 {} 包装成表达式, 即 ({}).

1
2
3
4
let studentRow = Array(3).fill(undefined).map(() => ({}));
studentRow[0].name = 'tony';
// > studentRow
// [ { name: 'tony' }, {}, {} ]

知识点:

  • 若函数体的表达式个数多于一个, 或者函数题包含非表达式语句的时候才需要用 {...} 包裹.

  • 如果只有一个表达式, 并且省略了 {...} 的话, 则附加一个隐式 return. 若在块体内需要指定返回值, 则需要明确的 return.

  • 箭头函数提供了简练的语法, 但不是普通函数的替代品. 箭头函数的主要设计目的是改变 this 的行为. 普通函数内的 this 是动态绑定, this 指向谁取决于调用者. 而箭头函数里的 this 是基于作用域的, 是可预测的.(可参考从游戏角度说作用域).

令人绝望的Array.prototype.fill()


你以为结束了, 其实才刚刚开始

这是真正的开始, 没看错, 是的, 我们之前所做的可能都是无用功.

Array.prototype.fill兼容性

是的, IE 是魔鬼. 费尽了周折, 才发现一切都是徒劳.
难道就这么放弃了?

‘放弃’能吃么? 能吃就吃了它, 啥? 不能吃?!? 提它作甚!!!

Array.prototype.fill() 方便之处就是能够简便填充数组. 此法不行, 另寻他法.

Function.prototype.apply() 了解一下


Function.prototype.apply() 入参有两个. 第一个参数是 函数方法 的调用者, 第二个参数是 函数方法 的入参(要区分入参和入参的不同). 函数方法 的入参可以是数组也可以是类数组. 我们的目的就是填充数组, 所以我们要在类数组上做文章. 就拿 console.log 做例子🌰. (直接复制我之前的博客内容😌).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function log_1(arg) {
console.log(arg)
}
log_1(1);
log_1(1,2,3);
// 1
// 1

// 改造下
function log_2() {
const log = console.log;
log.apply(null, arguments)
}
log_2(1);
log_2(1, 2, 3)
// 1
// 1 2 3

这是 Function.prototype.apply() 使用的方法. 如果我们把 log_2 里的 arguments 换成 {length: 3},

1
2
3
4
5
6
function log_2() {
const log = console.log;
log.apply(null, {length: 3})
}
log_2()
// undefined undefined undefined

{length: 3}[undefined, undefined, undefined] 在传入 apply(null;...) 后, 在参数的处理上, 最后的结果是一样的.
那么, Array(3).fill(undefined).map(() => ({})) 可改造成,

1
2
3
4
let studentRow = Array.apply(null, {length: 3}).map(() => ({}));
studentRow[0].name = 'tony';
// > studentRow
// [ { name: 'tony' }, {}, {} ]

在这里 Array 作为普通函数调用, 以上等同于

1
2
3
let studentRow = Array(undefined, undefined, undefined);
// > studentRow
// [ undefined, undefined, undefined ]

收尾


只是初始化一个空对象数组, 结果整出这么多幺蛾子.
处理数据其实就那么几行代码. 大致长这模样,

1
2
3
4
5
6
7
8
function handleData(params) {
const studentRow = Array.apply(null, {length: 3}).map(() => ({}));
params.forEach(item => {
studentRow[item.row-1][`row`] = item.row;
studentRow[item.row-1][`col_${item.col}`] = item.name;
})
return studentRow;
}
------------- The End -------------
显示评论