在 JavaScript 开发中,我们经常需要遍历对象的属性。但你是否遇到过这样的问题:
"为什么遍历一个简单对象时,会多出一些意想不到的方法?"
这是因为 for...in
循环会遍历对象自身 + 原型链上所有可枚举的属性 。如果我们只想获取对象"自己"的属性,就必须使用 hasOwnProperty()
方法进行过滤。
本文将深入讲解如何准确获取对象非原型链上的属性(即"自身属性"),并结合你提供的代码,给出最佳实践。
一、问题背景:for...in
的"陷阱"
看一个经典例子:
js
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function () {
console.log(`Hello, I'm ${this.name}`);
};
const person = new Person('Alice');
// 直接使用 for...in
for (let key in person) {
console.log(key);
}
// 输出:
// name
// sayHello ← 这是原型上的方法,但我们可能不想要它!
❌ 问题:sayHello
是从原型链继承来的,并非 person
实例自身的属性。
二、解决方案:使用 hasOwnProperty()
过滤
✅ 正确做法:使用 Object.prototype.hasOwnProperty()
方法判断属性是否属于对象自身。
js
function iterate(obj) {
const res = [];
for (let key in obj) {
// ✅ 只保留对象自身的属性
if (obj.hasOwnProperty(key)) {
res.push(key + ': ' + obj[key]);
}
}
return res;
}
const result = iterate(person);
console.log(result);
// 输出: ["name: Alice"]
// ✅ 成功过滤掉了原型上的 sayHello
三、hasOwnProperty
原理详解
✅ 什么是"自身属性"(Own Property)?
- 自身属性 :直接定义在对象实例上的属性,如
this.name = 'Alice'
; - 继承属性 :通过原型链从父级继承来的属性或方法,如
sayHello
。
✅ hasOwnProperty()
的作用
- 检查某个属性是否是对象的直接属性;
- 返回
true
表示该属性是自身的; - 返回
false
表示该属性来自原型链或不存在。
js
person.hasOwnProperty('name'); // true ← 自身属性
person.hasOwnProperty('sayHello'); // false ← 来自原型
person.hasOwnProperty('toString'); // false ← 来自 Object.prototype
四、更现代的替代方案
虽然 hasOwnProperty
非常经典,但现代 JavaScript 提供了更多选择:
✅ 方法1:Object.keys()
------ 获取所有自身可枚举属性
js
const ownKeys = Object.keys(person);
console.log(ownKeys); // ['name']
// 结合 map 处理
const result = Object.keys(person).map(key => {
return `${key}: ${person[key]}`;
});
✅ 优点:简洁,无需手动过滤; ❌ 缺点:只包含可枚举属性。
✅ 方法2:Object.getOwnPropertyNames()
------ 包括不可枚举属性
js
// 添加一个不可枚举属性
Object.defineProperty(person, 'age', {
value: 25,
enumerable: false
});
console.log(Object.keys(person)); // ['name']
console.log(Object.getOwnPropertyNames(person)); // ['name', 'age']
✅ 适用场景:需要获取
configurable: false
或enumerable: false
的属性。
✅ 方法3:Reflect.ownKeys()
------ 最全的自身属性列表
js
const obj = { a: 1 };
Object.defineProperty(obj, 'b', { value: 2, enumerable: false });
obj[Symbol('c')] = 3;
console.log(Reflect.ownKeys(obj)); // ['a', 'b', Symbol(c)]
✅ 包含:字符串键、Symbol 键、可枚举和不可枚举属性。
五、hasOwnProperty
的潜在风险与规避
⚠️ 风险:对象可能重写了 hasOwnProperty
js
const badObj = {
name: 'Test',
hasOwnProperty: function () {
return false; // 恶意重写
}
};
badObj.hasOwnProperty('name'); // false ❌ 错误结果!
✅ 安全调用方式
使用 call
或 Object.prototype.hasOwnProperty.call()
:
js
Object.prototype.hasOwnProperty.call(badObj, 'name'); // true ✅
// 或
{}.hasOwnProperty.call(badObj, 'name'); // true ✅
📌 推荐在库或通用代码中使用这种写法,确保健壮性。
六、完整工具函数推荐
结合你提供的 iterate
函数,我们可以优化为更健壮的版本:
js
function getOwnProperties(obj) {
const res = [];
// 使用安全的 hasOwnProperty 调用
for (let key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
res.push(`${key}: ${obj[key]}`);
}
}
return res;
}
// 或者使用现代 API
function getOwnPropertiesModern(obj) {
return Object.keys(obj).map(key => `${key}: ${obj[key]}`);
}
七、总结:获取对象自身属性的方法对比
方法 | 是否包含原型 | 是否包含不可枚举 | 是否包含 Symbol | 推荐场景 |
---|---|---|---|---|
for...in + hasOwnProperty |
❌ 否 | ❌ 否 | ❌ 否 | 兼容旧环境 |
Object.keys() |
❌ 否 | ❌ 否 | ❌ 否 | 日常开发,简洁 |
Object.getOwnPropertyNames() |
❌ 否 | ✅ 是 | ❌ 否 | 需要不可枚举属性 |
Reflect.ownKeys() |
❌ 否 | ✅ 是 | ✅ 是 | 全面获取所有键 |
💡 结语
"遍历对象时,
for...in
是'广撒网',hasOwnProperty
是'精准捕捞'。"
掌握如何区分自身属性 与继承属性,是写出高质量 JavaScript 代码的基本功。无论你是做数据处理、对象克隆,还是开发类库,这个知识点都至关重要。
📌 记住:
- 用
hasOwnProperty
过滤原型属性; - 优先使用
Object.keys()
等现代 API; - 在通用代码中使用
Object.prototype.hasOwnProperty.call()
保证安全。