首页 > web前端 > js教程 > 正文

js如何判断属性是否在原型上

小老鼠
发布: 2025-08-13 13:54:02
原创
233人浏览过

要判断javascript对象的属性是否来自原型链,最稳妥的方法是结合in操作符和object.prototype.hasownproperty.call()。1. 使用prop in obj检查属性是否存在于对象或其原型链上;2. 使用object.prototype.hasownproperty.call(obj, prop)判断属性是否为对象自身属性;3. 若前者为true且后者为false,则该属性来自原型链。此方法可准确区分实例属性与原型属性,避免因对象重写hasownproperty导致的判断错误,确保属性来源判断的可靠性,适用于深拷贝、序列化等需精确控制属性的场景。

js如何判断属性是否在原型上

要判断一个JavaScript对象的属性究竟是它自己的(实例属性)还是从原型链上继承来的,最常用也最稳妥的方法就是结合

in
登录后复制
操作符和
Object.prototype.hasOwnProperty.call()
登录后复制
。简单来说,如果一个属性存在于对象上(
prop in obj
登录后复制
为真),但它又不是对象自身的属性(
obj.hasOwnProperty(prop)
登录后复制
为假),那么这个属性就一定是从原型链上继承而来的。

js如何判断属性是否在原型上

解决方案

判断属性是否在原型上,核心思路就是利用

in
登录后复制
操作符和
hasOwnProperty
登录后复制
方法的不同行为。
in
登录后复制
操作符会检查对象本身及其整个原型链上是否存在某个属性,而
hasOwnProperty
登录后复制
方法则只检查属性是否直接存在于对象实例上,不考虑原型链。

所以,一个属性如果满足以下两个条件,就可以确定它来自原型:

js如何判断属性是否在原型上
  1. 属性存在于对象或其原型链上(
    propertyName in object
    登录后复制
    返回
    true
    登录后复制
    )。
  2. 属性不是对象自身的属性(
    object.hasOwnProperty(propertyName)
    登录后复制
    返回
    false
    登录后复制
    )。

结合起来就是:

!(obj.hasOwnProperty(prop)) && (prop in obj)
登录后复制

看个例子:

js如何判断属性是否在原型上
function Person(name) {
    this.name = name; // 实例属性
}

Person.prototype.greet = function() { // 原型属性
    console.log(`Hello, my name is ${this.name}`);
};

const person1 = new Person('Alice');
person1.age = 30; // 实例属性

// 检查 'name' 属性
console.log('--- 检查 name (实例属性) ---');
console.log(`'name' in person1: ${'name' in person1}`); // true
console.log(`person1.hasOwnProperty('name'): ${person1.hasOwnProperty('name')}`); // true
console.log(`'name' 在原型上吗? ${!person1.hasOwnProperty('name') && ('name' in person1)}`); // false

// 检查 'greet' 属性
console.log('\n--- 检查 greet (原型属性) ---');
console.log(`'greet' in person1: ${'greet' in person1}`); // true
console.log(`person1.hasOwnProperty('greet'): ${person1.hasOwnProperty('greet')}`); // false
console.log(`'greet' 在原型上吗? ${!person1.hasOwnProperty('greet') && ('greet' in person1)}`); // true

// 检查 'toString' 属性 (更深的原型链,来自 Object.prototype)
console.log('\n--- 检查 toString (更深的原型属性) ---');
console.log(`'toString' in person1: ${'toString' in person1}`); // true
console.log(`person1.hasOwnProperty('toString'): ${person1.hasOwnProperty('toString')}`); // false
console.log(`'toString' 在原型上吗? ${!person1.hasOwnProperty('toString') && ('toString' in person1)}`); // true

// 检查 'age' 属性 (实例属性)
console.log('\n--- 检查 age (实例属性) ---');
console.log(`'age' in person1: ${'age' in person1}`); // true
console.log(`person1.hasOwnProperty('age'): ${person1.hasOwnProperty('age')}`); // true
console.log(`'age' 在原型上吗? ${!person1.hasOwnProperty('age') && ('age' in person1)}`); // false

// 检查一个不存在的属性
console.log('\n--- 检查 nonexistent (不存在的属性) ---');
console.log(`'nonexistent' in person1: ${'nonexistent' in person1}`); // false
console.log(`person1.hasOwnProperty('nonexistent'): ${person1.hasOwnProperty('nonexistent')}`); // false
console.log(`'nonexistent' 在原型上吗? ${!person1.hasOwnProperty('nonexistent') && ('nonexistent' in person1)}`); // false
登录后复制

这里值得一提的是,

hasOwnProperty
登录后复制
方法本身也可能被对象覆盖。为了避免这种情况,更健壮的做法是使用
Object.prototype.hasOwnProperty.call(obj, prop)
登录后复制

const objWithOverriddenHasOwnProperty = {
    myProp: 1,
    hasOwnProperty: function() {
        return false; // 故意返回 false
    }
};

console.log('\n--- 使用 Object.prototype.hasOwnProperty.call() ---');
console.log(`objWithOverriddenHasOwnProperty.hasOwnProperty('myProp'): ${objWithOverriddenHasOwnProperty.hasOwnProperty('myProp')}`); // false (被覆盖了)
console.log(`Object.prototype.hasOwnProperty.call(objWithOverriddenHasOwnProperty, 'myProp'): ${Object.prototype.hasOwnProperty.call(objWithOverriddenHasOwnProperty, 'myProp')}`); // true (正确判断)

// 所以判断原型属性,更严谨的写法是:
const isPrototypeProperty = (obj, prop) => !Object.prototype.hasOwnProperty.call(obj, prop) && (prop in obj);

console.log(`'myProp' 在 objWithOverriddenHasOwnProperty 的原型上吗? ${isPrototypeProperty(objWithOverriddenHasOwnProperty, 'myProp')}`); // false (因为它是实例属性)
登录后复制

这样处理,哪怕遇到一些“奇奇怪怪”的对象,比如它们自己重写了

hasOwnProperty
登录后复制
,我们的判断逻辑依然能保持准确性。

为什么单靠
in
登录后复制
操作符不足以判断原型属性?

说实话,刚接触JavaScript的时候,我个人也曾被

in
登录后复制
操作符的行为搞得有点迷糊。它看起来很直观,但实际使用起来,它的“包容性”有时会让人产生误解。
in
登录后复制
操作符的职责是检查某个属性名是否存在于对象的可枚举属性(包括自身和原型链上的)中。它不会区分这个属性是直接在对象上定义的,还是从原型链上继承来的。

举个例子,

Object.prototype
登录后复制
上有很多我们常用的方法,比如
toString
登录后复制
valueOf
登录后复制
等等。当你创建一个空对象
{}
登录后复制
时,即使你没有明确定义这些方法,
'toString' in {}
登录后复制
依然会返回
true
登录后复制
。这正是因为它沿着原型链找到了
Object.prototype.toString
登录后复制

const myObj = {};
console.log(`'toString' in myObj: ${'toString' in myObj}`); // true
console.log(`myObj.hasOwnProperty('toString'): ${myObj.hasOwnProperty('toString')}`); // false
登录后复制

你看,

in
登录后复制
操作符告诉我们属性存在,但
hasOwnProperty
登录后复制
却说它不是
myObj
登录后复制
自己的。这就明确了
toString
登录后复制
是来自原型的。所以,如果只用
in
登录后复制
,你根本不知道这个属性是“亲生”的还是“借用”的。在很多需要精确控制属性来源的场景下,比如序列化对象、深度克隆或者遍历对象属性时,这种不区分来源的特性就会带来问题。

如知AI笔记
如知AI笔记

如知笔记——支持markdown的在线笔记,支持ai智能写作、AI搜索,支持DeepseekR1满血大模型

如知AI笔记 27
查看详情 如知AI笔记

除了
hasOwnProperty
登录后复制
,还有哪些方法可以探究属性来源?

当然有,JavaScript提供了好几种内省机制来帮助我们理解对象的结构。不过,它们通常用于更高级的场景,或者获取更详细的信息,而不仅仅是判断“是不是原型属性”。

一个很有用的方法是

Object.getPrototypeOf()
登录后复制
。这个方法会返回指定对象的原型(即
[[Prototype]]
登录后复制
内部属性的值)。你可以通过它向上遍历原型链,然后结合
Object.getOwnPropertyDescriptor()
登录后复制
来检查每个原型对象上是否有该属性。

function Vehicle() {}
Vehicle.prototype.wheels = 4;

function Car() {}
Car.prototype = Object.create(Vehicle.prototype); // Car 继承自 Vehicle
Car.prototype.constructor = Car;
Car.prototype.engine = 'V6';

const myCar = new Car();
myCar.color = 'red';

let currentProto = Object.getPrototypeOf(myCar); // 获取 myCar 的直接原型 (Car.prototype)
const propName = 'wheels';

let foundOnPrototype = false;
while (currentProto) {
    if (Object.prototype.hasOwnProperty.call(currentProto, propName)) {
        console.log(`${propName} 在 ${currentProto.constructor.name}.prototype 上找到。`);
        foundOnPrototype = true;
        break;
    }
    currentProto = Object.getPrototypeOf(currentProto); // 继续向上查找原型链
}
console.log(`'${propName}' 是原型属性吗?${foundOnPrototype}`); // true

// 检查一个实例属性
currentProto = Object.getPrototypeOf(myCar);
const instancePropName = 'color';
foundOnPrototype = false;
while (currentProto) {
    if (Object.prototype.hasOwnProperty.call(currentProto, instancePropName)) {
        foundOnPrototype = true;
        break;
    }
    currentProto = Object.getPrototypeOf(currentProto);
}
console.log(`'${instancePropName}' 是原型属性吗?${foundOnPrototype}`); // false
登录后复制

这种手动遍历原型链的方式,虽然比

hasOwnProperty
登录后复制
组合拳复杂,但在你需要知道属性具体在原型链的哪一层时,它就显得非常有用了。

另外,

Object.getOwnPropertyDescriptor(obj, propName)
登录后复制
可以获取一个属性的完整描述符(包括它的
value
登录后复制
writable
登录后复制
enumerable
登录后复制
configurable
登录后复制
等特性),但它只针对对象自身的属性。如果你想知道一个属性是否是原型属性,你可以先尝试用
Object.getOwnPropertyDescriptor(obj, propName)
登录后复制
,如果返回
undefined
登录后复制
,那说明它不是自身属性。接着,你可以沿着原型链向上,对每个原型对象调用
Object.getOwnPropertyDescriptor
登录后复制
,直到找到这个属性。这听起来有点绕,但对于需要深入了解属性特性的场景,它提供了最详细的信息。

在实际项目中,区分实例与原型属性有哪些具体用途?

在日常开发中,区分实例属性和原型属性并非只是为了满足好奇心,它在很多实际场景中都有着重要的意义。

一个非常典型的场景就是对象的序列化和深拷贝。当我们想把一个JavaScript对象转换成JSON字符串(比如通过

JSON.stringify()
登录后复制
),或者进行一次深拷贝时,通常我们只希望处理对象“自己”的属性,也就是实例属性。原型上的方法或属性,通常我们不希望被序列化进去,因为它们是共享的,而且很可能包含循环引用或者函数(JSON不支持函数)。如果这时候不加区分地遍历所有属性,就可能导致意料之外的结果,甚至报错。
for...in
登录后复制
循环会遍历所有可枚举的属性(包括原型链上的),因此在使用
for...in
登录后复制
时,几乎总是需要搭配
hasOwnProperty
登录后复制
来过滤掉原型属性,确保你只操作实例数据。

// 错误的深拷贝尝试 (会把原型属性也拷贝过去)
function badDeepClone(obj) {
    const newObj = {};
    for (const key in obj) {
        // 缺少 hasOwnProperty 检查
        if (typeof obj[key] === 'object' && obj[key] !== null) {
            newObj[key] = badDeepClone(obj[key]);
        } else {
            newObj[key] = obj[key];
        }
    }
    return newObj;
}

// 正确的深拷贝,只拷贝实例属性
function goodDeepClone(obj) {
    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }
    const newObj = Array.isArray(obj) ? [] : {};
    for (const key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) { // 关键:只处理自身属性
            if (typeof obj[key] === 'object' && obj[key] !== null) {
                newObj[key] = goodDeepClone(obj[key]);
            } else {
                newObj[key] = obj[key];
            }
        }
    }
    return newObj;
}
登录后复制

此外,在框架或库的开发中,这种区分更是基础。例如,当你设计一个继承体系时,你可能希望某些方法是共享的(放在原型上),而某些数据是每个实例独有的(放在实例上)。精确地控制属性的归属,能帮助你避免意外的数据共享问题,或者在调试时快速定位问题。

再比如,防止意外覆盖。如果你在对象实例上定义了一个与原型上同名的属性,那么实例属性会“遮蔽”原型属性。了解这一点,能帮助你避免无意中覆盖了原型上重要的方法或属性,从而导致程序行为异常。

总的来说,理解并能准确判断属性的来源,是深入理解JavaScript对象模型和原型链的关键一步。它不仅仅是理论知识,更是编写健壮、可维护代码的实际工具

以上就是js如何判断属性是否在原型上的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号