前端开发学习笔记(七) : JavaScript的Prototype Chain和Object
JavaScript的Prototype Chain和Object

JavaScript 的 原型链(Prototype Chain)对象(Object) 是语言的核心机制,深刻理解它们对掌握 JavaScript 的继承、属性查找和对象模型至关重要。以下从底层原理到实际应用进行全面解析:

一、对象(Object)的本质

JavaScript 中的对象是 键值对的集合,每个对象本质上是一个动态的容器,可以存储属性(数据)和方法(函数)。对象的特性包括:

  • 动态性:属性可以随时添加、删除或修改。
  • 引用类型:对象通过引用传递。
  • 可扩展性:可以通过原型继承扩展功能。

1. 对象的内部结构

每个对象包含:

  • 自身属性(Own Properties):直接定义在对象上的属性。
  • 隐藏属性 [[Prototype]]:指向其原型对象(通过 __proto__Object.getPrototypeOf() 访问)。
const obj = { a: 1 };
console.log(obj.__proto__ === Object.prototype); // true

二、原型链(Prototype Chain)的机制

原型链是 JavaScript 实现继承和属性查找的核心机制。其核心规则是:

  • 当访问对象的属性时,若对象自身没有该属性,会沿着 [[Prototype]] 链向上查找,直到找到或到达 null

1. 原型链的构建

// 父对象
const parent = { a: 1 };

// 子对象,通过 __proto__ 继承父对象
const child = { b: 2 };
child.__proto__ = parent; // 或使用 Object.setPrototypeOf(child, parent)

console.log(child.a); // 1(从 parent 继承)
console.log(child.b); // 2(自身属性)

原型链的终点

所有原型链的终点是 Object.prototype.__proto__,即 null

console.log(Object.getPrototypeOf(Object.prototype)); // null

2. 属性查找的完整流程

当访问 obj.property 时:

  1. 检查 obj 自身是否有 property
  2. 若没有,沿着 obj.__proto__ 向上查找。
  3. 直到找到 property 或到达 null(返回 undefined)。

三、构造函数、原型与实例的关系

JavaScript 通过 构造函数(Constructor)原型对象(Prototype) 实现类式继承。

1. 构造函数的作用

构造函数用于创建实例,并通过 new 关键字初始化对象:

function Person(name) {
  this.name = name; // 实例属性
}

// 方法定义在原型上(节省内存)
Person.prototype.sayHello = function () {
  console.log(`Hello, ${this.name}!`);
};

const john = new Person("John");
john.sayHello(); // "Hello, John!"

2. 关键三要素

  • 构造函数(Constructor)Person
  • 原型对象(Prototype)Person.prototype
  • 实例(Instance)john

三者关系:

john.__proto__ === Person.prototype; // true
Person.prototype.constructor === Person; // true

3. 原型链的完整结构

实例 (john)
  │
  ├── __proto__ → Person.prototype
  │               ├── sayHello()
  │               └── __proto__ → Object.prototype
  │                                  ├── toString()
  │                                  └── __proto__ → null

四、原型链的操作方法

1. 创建对象并指定原型

  • Object.create(proto):直接创建以 proto 为原型的对象。
    const parent = { a: 1 };
    const child = Object.create(parent);
    

2. 修改原型

  • Object.setPrototypeOf(obj, proto):动态修改对象的原型(性能较差,慎用)。
    Object.setPrototypeOf(child, parent);
    

3. 检测原型关系

  • instanceof:检查构造函数是否在原型链中。
    console.log(john instanceof Person); // true
    
  • Object.prototype.isPrototypeOf():检查对象是否在原型链中。
    console.log(Person.prototype.isPrototypeOf(john)); // true
    

五、原型链的典型应用

1. 实现继承

通过原型链实现子类继承父类:

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

Animal.prototype.eat = function () {
  console.log(`${this.name} is eating.`);
};

function Dog(name) {
  Animal.call(this, name); // 继承属性
}

// 继承方法:设置原型链
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // 修复构造函数指向

const dog = new Dog("Rex");
dog.eat(); // "Rex is eating."

2. 共享方法

将方法定义在原型上,所有实例共享同一方法(节省内存):

function Car(model) {
  this.model = model;
}

Car.prototype.drive = function () {
  console.log(`${this.model} is driving.`);
};

const car1 = new Car("Tesla");
const car2 = new Car("BMW");
// car1.drive 和 car2.drive 指向同一个函数

六、ES6 类(Class)与原型链

ES6 的 class 是语法糖,底层仍基于原型链:

class Person {
  constructor(name) {
    this.name = name;
  }
  sayHello() {
    console.log(`Hello, ${this.name}!`);
  }
}

// 等价于:
function Person(name) {
  this.name = name;
}
Person.prototype.sayHello = function() { ... };

七、关键注意事项

1. 原型污染

修改内置原型(如 Object.prototype)会影响所有对象:

Object.prototype.foo = "bar";
const obj = {};
console.log(obj.foo); // "bar"(所有对象都会继承)

2. 性能问题

过长的原型链会导致属性查找变慢,应尽量扁平化结构。

3. for...in 的陷阱

for...in 会遍历原型链上的可枚举属性:

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

for (const key in child) {
  console.log(key); // "b", "a"(包括原型链属性)
}

// 过滤自身属性:
for (const key in child) {
  if (child.hasOwnProperty(key)) {
    console.log(key); // "b"
  }
}

八、总结

  • 对象是动态的键值对集合,通过原型链实现继承。
  • 原型链是 JavaScript 属性查找的机制,核心是 [[Prototype]]
  • 构造函数通过原型对象共享方法,new 创建实例。
  • ES6 类是原型链的语法糖,简化了继承的写法。

理解原型链和对象模型是掌握 JavaScript 的核心,也是避免隐式 Bug 的关键!

在 JavaScript 的面试或考试中,原型链(Prototype Chain)对象(Object) 是高频考点。以下是可能涉及的考点及对应的核心内容,附示例代码和重点解析:

一、基础概念考点

1. 原型链的定义与作用

  • 考点:解释原型链的概念和属性查找机制。
  • 答案要点
    • 每个对象都有 [[Prototype]](通过 __proto__Object.getPrototypeOf() 访问)。
    • 属性查找时,若对象自身没有该属性,会沿原型链向上查找,直到 null
    const parent = { a: 1 };
    const child = { b: 2 };
    Object.setPrototypeOf(child, parent);
    console.log(child.a); // 1(从 parent 继承)
    

2. 原型链的终点

  • 考点:原型链的终点是什么?
  • 答案要点
    • 所有原型链的终点是 null
    console.log(Object.prototype.__proto__); // null
    

二、对象与构造函数考点

3. 构造函数与原型对象的关系

  • 考点:构造函数、原型对象、实例之间的关系。
  • 答案要点
    • 构造函数通过 new 创建实例。
    • 实例的 __proto__ 指向构造函数的 prototype
    function Person(name) {
      this.name = name;
    }
    const john = new Person("John");
    console.log(john.__proto__ === Person.prototype); // true
    console.log(Person.prototype.constructor === Person); // true
    

4. new 操作符的作用

  • 考点new 操作符做了什么?
  • 答案要点
    1. 创建一个新对象。
    2. 将新对象的 __proto__ 指向构造函数的 prototype
    3. 执行构造函数(绑定 this)。
    4. 返回新对象(若构造函数未显式返回对象)。

三、继承与原型链考点

5. 原型链继承的缺陷

  • 考点:原型链继承的问题(如引用类型共享)。

  • 示例代码

    function Parent() {
      this.nums = [1, 2];
    }
    function Child() {}
    Child.prototype = new Parent();
    
    const c1 = new Child();
    const c2 = new Child();
    c1.nums.push(3);
    console.log(c2.nums); // [1, 2, 3](所有实例共享引用属性)
    

6. 组合继承(经典继承)

  • 考点:如何通过构造函数 + 原型链实现继承?

  • 答案要点

    • 使用构造函数继承属性,原型链继承方法。
    function Parent(name) {
      this.name = name;
    }
    Parent.prototype.say = function () {
      console.log(this.name);
    };
    
    function Child(name, age) {
      Parent.call(this, name); // 继承属性
      this.age = age;
    }
    Child.prototype = Object.create(Parent.prototype); // 继承方法
    Child.prototype.constructor = Child; // 修复构造函数
    

四、ES6 类与原型链考点

7. class 关键字的本质

  • 考点:ES6 类与原型链的关系。

  • 答案要点

    • class 是语法糖,底层基于原型链。
    class Person {
      constructor(name) { this.name = name; }
      say() { console.log(this.name); }
    }
    
    // 等价于:
    function Person(name) { this.name = name; }
    Person.prototype.say = function() { ... };
    

8. super 关键字的作用

  • 考点super 在继承中的行为。

  • 示例代码

    class Parent {
      constructor(name) {
        this.name = name;
      }
    }
    
    class Child extends Parent {
      constructor(name, age) {
        super(name); // 必须调用 super() 才能使用 this
        this.age = age;
      }
    }
    

五、对象操作考点

9. Object.create() 的作用

  • 考点:如何创建一个指定原型的对象?
  • 示例代码
    const proto = { a: 1 };
    const obj = Object.create(proto);
    console.log(obj.a); // 1(继承自 proto)
    

10. Object.keys() vs for...in

  • 考点:两者的区别。

  • 答案要点

    • Object.keys() 返回对象自身可枚举属性。
    • for...in 遍历对象自身及原型链上的可枚举属性。
    const obj = { a: 1 };
    const child = Object.create(obj);
    child.b = 2;
    
    console.log(Object.keys(child)); // ["b"]
    for (const key in child) {
      console.log(key);
    } // "b", "a"
    

六、高级考点

11. 手写 Object.create()

  • 考点:实现 Object.create 的 Polyfill。
  • 答案代码
    if (!Object.create) {
      Object.create = function (proto) {
        function F() {}
        F.prototype = proto;
        return new F();
      };
    }
    

12. 原型污染与防御

  • 考点:如何防止原型污染?
  • 答案要点
    • 避免直接修改 Object.prototype
    • 使用 Object.freeze() 冻结原型。
    Object.freeze(Object.prototype); // 禁止修改
    

七、实战代码题

13. 实现一个简单的继承

  • 题目:用原型链实现 AnimalDog 的继承。

  • 答案代码

    function Animal(name) {
      this.name = name;
    }
    Animal.prototype.eat = function () {
      console.log("Eating...");
    };
    
    function Dog(name, breed) {
      Animal.call(this, name);
      this.breed = breed;
    }
    Dog.prototype = Object.create(Animal.prototype);
    Dog.prototype.constructor = Dog;
    Dog.prototype.bark = function () {
      console.log("Woof!");
    };
    
    const dog = new Dog("Rex", "Husky");
    dog.eat(); // "Eating..."
    

最后修改于 2025-03-21