原型与原型链

7 阅读2分钟

一、原型的核心概念与本质

定义:原型是JavaScript中对象的一个内置属性,用于实现对象间的属性共享。每个函数创建时都会自动生成一个prototype属性,而每个对象(除null外)都有一个__proto__(隐式原型)指向其构造函数的原型对象。

二、原型链的工作机制

1. 属性查找流程

当访问对象的属性时,JavaScript会按以下顺序查找:

  1. 对象自身是否有该属性;
  2. 若没有,查找对象的__proto__(隐式原型);
  3. 继续向上查找原型链,直到Object.prototype
  4. 若仍未找到,返回undefined

示例

function Person(name) {
  this.name = name;
}
Person.prototype.sayName = function() {
  return `Hello, ${this.name}`;
};

const person = new Person('Alice');
console.log(person.sayName()); // 查找流程:person自身→person.__proto__(Person.prototype)→找到sayName
2. 原型链的终点
  • Object.prototype.__proto__ === null,标志着原型链的结束。

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

角色作用关键属性
构造函数创建实例的函数(如Personprototype:指向原型对象
原型对象共享属性的载体(如Person.prototypeconstructor:反向指向构造函数
实例构造函数创建的对象(如person__proto__:指向原型对象

四、原型的核心方法与应用

1. 原型链的核心方法
  • Object.create(proto[, propertiesObject])
    创建新对象,其__proto__指向指定原型。

    const animal = {
      eat: function() { console.log('Eating...'); }
    };
    const dog = Object.create(animal);
    dog.bark = function() { console.log('Woof!'); };
    dog.eat(); // 继承自animal原型
    
  • instanceof操作符
    检查实例的原型链中是否存在某个构造函数的原型对象。

    console.log(person instanceof Person); // true
    console.log(person instanceof Object); // true(原型链最终指向Object.prototype)
    
  • hasOwnProperty(key)
    检测属性是否为对象自身所有(不查找原型链)。

    person.hasOwnProperty('name'); // true(自身属性)
    person.hasOwnProperty('sayName'); // false(原型属性)
    
2. 原型链的应用场景
  • 类的继承:通过原型链实现“继承”(如Child.prototype = new Parent());
  • 属性共享:避免重复定义方法(如所有数组实例共享Array.prototype.push);
  • polyfill实现:通过修改原型链添加浏览器缺失的方法(如Object.assign的polyfill)。

五、原型链的缺陷与ES6类的改进

1. 原型链的主要问题
  • 共享引用类型属性的风险

    function Parent() {
      this.friends = ['Alice', 'Bob'];
    }
    function Child() {}
    Child.prototype = new Parent(); // 继承
    
    const child1 = new Child();
    const child2 = new Child();
    child1.friends.push('Charlie');
    console.log(child2.friends); // ['Alice', 'Bob', 'Charlie'](共享引用导致意外修改)
    
  • 创建实例时无法向原型构造函数传参
    原型链继承中,Child.prototype = new Parent()无法传递参数给Parent

2. ES6类(class)对原型链的封装
// ES6类语法(底层仍基于原型链)
class Parent {
  constructor(name) {
    this.name = name;
  }
  sayHi() {
    console.log(`Hi, ${this.name}`);
  }
}

class Child extends Parent {
  constructor(name, age) {
    super(name); // 调用父类构造函数
    this.age = age;
  }
}

const child = new Child('David', 18);
child.sayHi(); // Hi, David(通过原型链调用Parent的方法)

改进点

  • 通过classextends语法简化原型链操作;
  • super关键字明确调用父类方法,解决参数传递问题;
  • 语法更接近传统面向对象编程,降低原型链使用门槛。

六、问题

1. 问:如何理解原型链的工作原理?请用代码示例说明。
 原型链是对象查找属性的路径,当访问`obj.prop`时,JS先查自身属性,再沿`__proto__`向上查找。例如:  
 ```javascript
 const obj = { x: 1 };
 obj.__proto__ = { y: 2 };
 obj.__proto__.__proto__ = { z: 3 };
 console.log(obj.x); // 1(自身属性)
 console.log(obj.y); // 2(原型属性)
 console.log(obj.z); // 3(原型的原型属性)
 ```  
2. 问:构造函数的prototype与实例的__proto__有什么关系?
 构造函数的`prototype`属性指向原型对象,而实例的`__proto__`(隐式原型)默认指向该构造函数的原型对象。即:  
 ```javascript
 function F() {}
 const f = new F();
 f.__proto__ === F.prototype; // true
 F.prototype.constructor === F; // true
 ```  
3. 问:为什么null没有原型?
 `null`是JS中的特殊值,设计上不希望对其进行属性操作,因此`null.__proto__``undefined`,原型链在`Object.prototype.__proto__ === null`处终止,形成闭环。  

七、总结

原型是对象间共享属性的机制,原型链是属性查找的路径;
构造函数的prototype与实例的__proto__指向同一原型对象;
instanceof检查原型链,hasOwnProperty仅查自身属性;
ES6类是原型链的语法糖,未改变底层机制;
面试中结合具体场景(如继承、属性查找)说明,避免死记硬背。