JavaScript 原型继承与属性访问规则详解

发布于:2025-08-19 ⋅ 阅读:(43) ⋅ 点赞:(0)

一、原型继承基本概念

JavaScript 使用原型继承(Prototypal Inheritance)作为其面向对象编程的基础,这与传统的基于类的继承有本质区别。

1. 原型链(Prototype Chain)

每个 JavaScript 对象都有一个内部链接指向另一个对象,称为它的原型(prototype)。当访问对象的属性时,如果对象自身没有该属性,就会沿着原型链向上查找。

function Animal(name) {
  this.name = name;
}

Animal.prototype.speak = function() {
  console.log(`${this.name} makes a noise.`);
};

const dog = new Animal('Dog');
dog.speak(); // 先在dog实例查找,然后在Animal.prototype查找

2. 关键对象和属性

  • __proto__(现已标准化为 Object.getPrototypeOf()):对象的实际原型

  • prototype:构造函数特有的属性,用于设置新创建实例的原型

  • constructor:指向创建该对象的构造函数

二、属性访问规则

1. 属性查找机制

当访问对象属性时,JavaScript 引擎遵循以下顺序:

  1. 检查对象自身属性(包括可枚举和不可枚举属性)

  2. 如果未找到,沿着原型链向上查找

  3. 直到 Object.prototype(所有对象的顶层原型,其 __proto__ 为 null

  4. 如果最终未找到,返回 undefined

const parent = { a: 1 };
const child = Object.create(parent);
child.b = 2;

console.log(child.b); // 2 (自身属性)
console.log(child.a); // 1 (继承属性)
console.log(child.c); // undefined (不存在)

2. 属性遮蔽(Property Shadowing)

如果对象和它的原型有同名属性,对象自身的属性会遮蔽原型上的属性

const proto = { value: 10 };
const obj = Object.create(proto);
obj.value = 20; // 遮蔽原型属性

console.log(obj.value); // 20 (自身属性)
delete obj.value;
console.log(obj.value); // 10 (恢复访问原型属性)

三、原型继承的实现方式

1. 构造函数模式

function Person(name) {
  this.name = name;
}

Person.prototype.greet = function() {
  console.log(`Hello, I'm ${this.name}`);
};

const john = new Person('John');
john.greet(); // Hello, I'm John

2. Object.create()

const animal = {
  eat() {
    console.log(`${this.name} eats.`);
  }
};

const rabbit = Object.create(animal, {
  name: { value: 'White Rabbit' }
});

rabbit.eat(); // White Rabbit eats.

3. ES6 Class 语法糖

class Animal {
  constructor(name) {
    this.name = name;
  }
  
  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}

class Dog extends Animal {
  speak() {
    console.log(`${this.name} barks.`);
  }
}

const d = new Dog('Rex');
d.speak(); // Rex barks.

四、属性访问的特殊情况

1. 不可写属性

如果原型属性被标记为 writable: false,子对象无法修改该属性:

const proto = {};
Object.defineProperty(proto, 'value', {
  value: 10,
  writable: false
});

const obj = Object.create(proto);
obj.value = 20; // 严格模式下会报错

console.log(obj.value); // 10 (修改失败)

2. setter/getter 继承

const proto = {
  get value() {
    return this._value * 2;
  },
  set value(v) {
    this._value = v;
  }
};

const obj = Object.create(proto);
obj.value = 10;
console.log(obj.value); // 20

五、原型相关方法

1. 检查原型关系

// 检查对象是否是另一个对象的原型
console.log(proto.isPrototypeOf(obj)); // true

// 检查实例关系
console.log(obj instanceof Object); // true

2. 获取/设置原型

// 获取原型
const proto = Object.getPrototypeOf(obj);

// 设置原型(性能差,不推荐)
Object.setPrototypeOf(obj, newProto);

六、最佳实践

  1. 不要直接修改内置对象原型(如 Array.prototype

  2. 优先使用 Object.create() 而非 __proto__

  3. 对于性能敏感代码,避免长原型链

  4. 考虑使用 ES6 Class 语法提高可读性

七、原型继承图示

实例对象 (obj) → 构造函数.prototype → Object.prototype → null
    ↑                   ↑
    |__proto__          |__proto__

JavaScript 的原型继承提供了极大的灵活性,理解其工作原理对于编写高效、可维护的代码至关重要。


网站公告

今日签到

点亮在社区的每一天
去签到