数组扩展
- 问题
- 解析
- 解决办法
- 总结
- 进一步扩展原因
问题
下列代码中的points是从本地存储中获取到的数据,我想存储到一个Map并且新增元素的时候报错
let obj = this.objectsManager._objects.get(obstacle.uuid);let points = obj.track_points;this.dyObstacleTP.set(obstacle.uuid,points);
"Cannot add property 3, object is not extensible"。
解析
在 JavaScript 中,如果你尝试向一个不可扩展的对象(例如被冻结或密封的数组)添加新属性或元素时,会遇到错误 “Cannot add property 3, object is not extensible”。
这通常是因为使用了 Object.freeze(), Object.seal() 或 Object.preventExtensions() 方法之一。
以下是这三个方法的简述:
-
Object.freeze(obj):这个方法会冻结一个对象,这意味着你不能对这个对象添加新的属性,也不能修改或删除已有的属性。
-
Object.seal(obj):这个方法会密封一个对象,这意味着你不能添加或删除属性,但可以修改已有的属性。
-
Object.preventExtensions(obj):这个方法会阻止任何新的属性被添加到一个对象上,但已有的属性可以被修改或删除。
下面是一个示例代码,它会触发类似的错误:
let frozenArray = [1, 2, 3];
Object.freeze(frozenArray); // 冻结数组try {frozenArray.push(4); // 尝试添加一个新元素
} catch (e) {console.error(e.message); // 捕获并打印错误信息
}
这段代码尝试向一个被冻结的数组 frozenArray 添加一个新元素,将会抛出一个错误,因为冻结的数组是不可扩展的。
如果遇到这个问题,需要确认自己是否故意使数组不可扩展。如果不希望数组是不可扩展的,应该避免使用上述方法,或者确保在需要修改数组之前没有调用它们。
解决办法
如果数组是不小心变得不可扩展的,而我们又需要修改它,这时候可能需要创建一个新的数组来替换原先的不可扩展的数组。这样做可以通过解构赋值
、Array.slice()
,或者其它数组复制技术
来完成。比如使用解构赋值:
let newArray = [...frozenArray, 4]; // 创建一个新数组并添加新元素
请注意,如果对象是冻结的,那么你就不能改变这个状态了,因为冻结对象是不可逆的。对于密封或是非扩展的对象,没有内置的方法可以使它们重新变成正常的可扩展对象,所以需要创建新对象作为替代。
let obj = this.objectsManager._objects.get(obstacle.uuid);let points = obj.track_points;this.dyObstacleTP.set(obstacle.uuid,[...points]);
总结
Array.slice()
是一个用来复制数组的方法,可以返回数组的一个浅拷贝,原数组不会被修改。它通常用于创建一个新的数组副本,或者从现有数组中提取一个子集。
Array.slice()
let originalArray = [1, 2, 3, 4, 5];
let copiedArray = originalArray.slice(); // 复制整个数组console.log(copiedArray); // [1, 2, 3, 4, 5]
slice 方法可以接受两个参数:start 和 end,分别表示开始和结束的索引(不包含结束索引所指向的元素)。如果省略这些参数,就会复制整个数组。
解构赋值
利用 ES6 引入的解构赋值,我们也可以简洁地复制数组:
let originalArray = [1, 2, 3, 4, 5];
let copiedArray = [...originalArray]; // 使用展开运算符进行复制console.log(copiedArray); // [1, 2, 3, 4, 5]
Array.from()
Array.from() 方法可以从类数组对象或可迭代对象创建一个新的、浅拷贝的数组实例:
let originalArray = [1, 2, 3, 4, 5];
let copiedArray = Array.from(originalArray);console.log(copiedArray); // [1, 2, 3, 4, 5]
数组的 map 或 filter 方法
虽然它们不是为了复制数组而设计的,但是 map 或 filter 方法也可以返回一个新数组,因此可以通过传递一个返回每个原始元素的函数来复制数组:
let originalArray = [1, 2, 3, 4, 5];
let copiedArray = originalArray.map(e => e);console.log(copiedArray); // [1, 2, 3, 4, 5]
循环复制
传统的循环方法也能够用来复制数组:
let originalArray = [1, 2, 3, 4, 5];
let copiedArray = [];for (let i = 0; i < originalArray.length; i++) {copiedArray.push(originalArray[i]);
}console.log(copiedArray); // [1, 2, 3, 4, 5]
所有这些技术都会产生原数组的一个浅拷贝。这意味着数组里面的基础数据类型(例如 numbers, strings, booleans)将会被完全复制,但是对象类型(例如 arrays, functions, 或者其它 objects)则是通过引用复制的。如果你需要一个深拷贝
,即复制数组以及数组内部嵌套的对象,那么你可能需要使用其它解决方案,如 JSON.parse(JSON.stringify(array)) 或者使用专门的深拷贝库函数。但请注意,使用 JSON.parse(JSON.stringify(array)) 会有局限性,比如它不能复制函数、正则表达式等特殊对象,并且会忽略 undefined 值。
进一步扩展原因
从 localStorage 中获取的数据默认是以字符串形式存储的。如果你尝试直接修改从 localStorage 中取回的数组数据,而没有先对其进行正确的解析,就有可能遇到问题。
例如,假设你有一个名为 points 的数组,你将其存入了 localStorage:
let points = [1, 2, 3];
localStorage.setItem('points', JSON.stringify(points));
当你从 localStorage 中检索这个数组时,需要使用 JSON.parse() 将其转换回数组格式,因为存储时数组会被转换成了字符串:
let storedPoints = localStorage.getItem('points');
if (storedPoints !== null) {points = JSON.parse(storedPoints);
}
现在 points 是一个真正的 JavaScript 数组,你可以像通常那样操作它,包括使用 push 方法来添加新元素。
但是,如果你没有进行 JSON.parse() 操作,你将得到一个表示数组内容的字符串,而不是实际的数组对象。字符串类型在 JavaScript 中是不可扩展的,也不支持 push 方法,所以尝试对其使用 push 方法会导致错误。
如果你在操作一个经过 JSON.parse() 解析后的数组还遇到 “Cannot add property x, object is not extensible” 这样的错误,那么可能你之前的某些代码已经使得这个数组变成了不可扩展的状态(通过 Object.freeze(), Object.seal() 或 Object.preventExtensions() 等)。检查你的代码,确认是否有地方对数组进行了这样的操作。
要有效地解决问题,你需要确保两件事情:
- 当你从 localStorage 获取数据时,确保你正确地将其从字符串解析为 JavaScript 对象或数组。
- 确认并排除任何可能会使数组不可扩展的代码,如 Object.freeze(), Object.seal() 或 Object.preventExtensions()。