面试必考题:JavaScript 如何查找对象属性?一文带你彻底理解原型链

5 阅读4分钟

JavaScript 原型链与属性查找:从懵圈到豁然开朗

原型链是 JavaScript 的核心概念之一,也是很多初学者容易混淆的地方。本文将用最直白的语言和生动的比喻,带你彻底理解 JS 的原型链机制和属性查找规则。


作为 JavaScript 初学者,你一定遇到过这样的困惑:为什么一个空的对象 {},竟然可以直接调用 .toString() 方法?这背后其实隐藏着 JS 的一个重要机制 —— 原型链(Prototype Chain)

今天,我们就来揭开它的神秘面纱!


什么是原型链?

简单来说,每个 JavaScript 对象都有一个内部链接,指向另一个对象,这个对象被称为它的 原型(Prototype) 。当我们试图访问对象的一个属性时,如果对象本身没有这个属性,JavaScript 引擎就会去它的原型上查找。如果原型上也没有,就再往上找原型的原型…… 这样层层递进,形成了一条“链条”,这就是所谓的“原型链”。


核心规则:原型链只在“读取”时生效

这是理解原型链的关键点:原型链的查找机制,仅在你尝试获取(读取)一个对象的属性值时才会被触发。

const obj = {};

// 1. 读取操作,会触发原型链查找
console.log(obj.toString); // 找到了,来自 Object.prototype
console.log(obj.someNonExistentProperty); //  找不到,返回 undefined

// 2. 写入操作,不会触发原型链查找
obj.newProp = 'I am new!';
// 此时 obj 自己就拥有了 newProp 属性,和原型无关

属性查找的详细流程

当你执行 obj.someProperty 时,JavaScript 引擎会按以下步骤进行查找:

  1. 检查对象自身:首先检查 obj 自身有没有名为 someProperty 的属性。
  2. 查找原型:如果没有,就去看 obj 的原型(即 obj.__proto__Object.getPrototypeOf(obj))上有没有这个属性。
  3. 继续向上:如果原型上也没有,就再看原型的原型(即 Object.getPrototypeOf(原型))上有没有。
  4. 直到终点:这个过程会一直持续,直到查找到 Object.prototype 为止(它是所有对象的“祖宗”,其 __proto__ 值为 null)。
  5. 返回结果:如果整条链都找完了还是没找到,表达式的结果就是 undefined

实例演示:一步步看查找过程

让我们通过一个具体的例子来加深理解:

// 1. 定义一个构造函数
function Person(name) {
    this.name = name; // 实例自身的属性
}

// 2. 给构造函数的原型添加方法
Person.prototype.sayHello = function() {
    console.log(`Hello, I'm ${this.name}`);
};

// 3. 创建一个实例
const alice = new Person("Alice");

// 4. 测试属性查找
console.log(alice.name);          //  "Alice" - 来自实例自身
console.log(alice.sayHello);      //  function - 来自 Person.prototype
alice.sayHello();                 // "Hello, I'm Alice"

// 5. 访问一个实例和原型都没有的属性
console.log(alice.age);           //  undefined - 整个原型链都找不到

// 6. 访问 Object.prototype 上的方法
console.log(alice.toString);      //  function - 来自 Object.prototype

查找过程解析

  • alice.name: alice 自己有 name,直接返回 "Alice"
  • alice.sayHello: alice 自己没有 sayHello,于是查找 alice.__proto__(即 Person.prototype),找到了,返回函数。
  • alice.age: alice 没有,Person.prototype 也没有,继续查 Object.prototype,还是没有,返回 undefined
  • alice.toString: alice 没有,Person.prototype 也没有,但 Object.prototype 上有 toString 方法,因此成功调用。

类比理解(助记)

想象一下你在图书馆找一本专业书籍:

  1. 先查自己的书架(对象自身):看看自己有没有收藏。
  2. 问室友(原型):没有的话,问室友借过没。
  3. 问班长(原型的原型):室友也没有,就去问班长。
  4. 问图书管理员Object.prototype):班长也不知道,最后只能求助图书管理员。
  5. 找不到(返回 undefined):管理员说图书馆没有这本书,那你就借不到了。

总结与延伸
  • 原型链的核心:是一种实现对象间属性共享和继承的机制。

  • 属性查找规则:只发生在读取操作时,遵循“就近原则”,逐级向上查找。

  • 终点:所有普通对象的原型链最终都会指向 Object.prototype

  • 判断自有属性:可以用 hasOwnProperty() 方法判断一个属性是否属于对象自身,而非继承而来。

    console.log(alice.hasOwnProperty('name'));     // true
    console.log(alice.hasOwnProperty('sayHello')); // false
    
  • 检测属性存在性in 操作符会检查整个原型链。

    console.log('toString' in alice); // true
    console.log('age' in alice);      // false
    

理解了原型链,你对 JavaScript 的对象模型和继承机制就有了更深一层的认识。这是进阶 JS 必须掌握的基础知识!