问题
为了方便调用,我们一般都会定义一些自定义的对象原型方法,比如下面这个:
Array.prototype.remove = function (callback) {
const index = this.findIndex(callback);
if (index > -1) {
this.splice(index, 1);
}
return index;
}
上面的代码定义了一个数组的删除方法,调用方便快捷。由于是定义在对象原型上,那就意味着所有的数组对象都具备这个方法。然而我们在用for...in
去遍历这个数组的时候,就会导致remove的方法名也被遍历出来,如下:
for(let i in ['a','b','c']){
console.log(i)
}
// 0
// 1
// 2
// remove
导致出现这种情况的原因在于我们通过上面那种方式去添加自定义对象原型方法的时候,该方法在原型链上默认是可枚举的属性。而for...in
循环会遍历对象的恰恰是可枚举的属性,包括对象的原型链上的属性。
数组的原型链
数组也是对象,因此它们也遵循原型链的概念。在 JavaScript 中,数组是通过构造函数
Array
创建的对象。数组的原型是Array.prototype
。而Array.prototype
的原型是Object.prototype
,最终Object.prototype
的原型是null
,形成了原型链。需要注意的是,数组具有自己的一些方法,例如
push
、pop
、forEach
等,这些方法是定义在Array.prototype
上的。因此,当你使用数组的方法时,实际上是通过原型链找到这些方法。这里push
和forEach
方法都是通过原型链从Array.prototype
继承而来的。原型链的概念使得 JavaScript 中的对象能够实现继承和共享方法。
解决方案
解决思路有两种,一种是通过别的遍历方式去替代for...in
,另一种则是确保自定义对象原型方法不被遍历
方法一 替代for…in
我们可以使用 for...of
循环或 Array.forEach
方法,它们只会遍历数组的实际元素,而不包括原型链上的属性。
const myArray = [1, 2, 3];
// 使用 for...of 循环
for (const element of myArray) {
console.log(element); // 输出 1, 2, 3
}
// 或者使用 forEach 方法
myArray.forEach(element => {
console.log(element); // 输出 1, 2, 3
});
方法二 确保自定义对象原型方法不被遍历
为了确保自定义原型方法不被遍历,拿主要的思路就是将这个方法改为不可枚举的属性。我们可以通过 Object.defineProperty
来定义。通过修改不可枚举属性不仅不会出现在 for...in
循环中,也不会被 Object.keys()
、Object.values()
和 Object.entries()
包含。这样,就可以在原型上定义方法,但不会在常见的遍历方法中暴露出来。
Object.defineProperty(Array.prototype, "remove", {
value(callback) {
const index = this.findIndex(callback);
if (index > -1) {
this.splice(index, 1);
}
return index;
},
enumerable: false, // 将属性设置为不可枚举
writable: true,
configurable: true
});
for(let i in ['a','b','c']){
console.log(i)
}
// 0
// 1
// 2
可以通过
Object.getOwnPropertyDescriptor
查看对象原型属性是否可以枚举:Object.getOwnPropertyDescriptor(Array.prototype,'remove'); //{writable: true, enumerable: false, configurable: true, value: ƒ} Object.getOwnPropertyDescriptor(Array.prototype,'push'); //{writable: true, enumerable: false, configurable: true, value: ƒ}