一文吃透 JavaScript 原型链:__proto__ 与 prototype 的终极区别!

630 阅读3分钟

一、基本概念对比:proto 与 prototype 简明区别

特性prototype__proto__
归属构造函数(Function)的一个属性所有对象(包括函数对象)都有的属性
作用实例化时,作为新对象的原型对象指向该对象的原型,即其构造函数的 prototype
定义构造函数拥有的默认属性各类对象(实例、数组、函数等)自带
主要用途提供共享属性、方法给实例形成原型链中的一环,属性查找委托
影响对象所有通过该构造函数 new 出来的实例当前对象的原型引用

二、实例演示:一步步看懂它们的关系

让我们看一段代码,逐步揭示它们在对象、函数、原型链中的真实联系:

function Animal(name) {
  this.name = name;
}
// 给 Animal 的 prototype 添加方法
Animal.prototype.sayHello = function() {
  console.log("Hello, I am " + this.name);
};
const cat = new Animal("Kitty");

console.log(cat.__proto__ === Animal.prototype); // true
console.log(Animal.prototype.constructor === Animal); // true
console.log(Animal.__proto__ === Function.prototype); // true
console.log(Animal.prototype.__proto__ === Object.prototype); // true

详解:这一系列等式代表什么?

  • cat.__proto__ —— 指向构造函数的 prototype,即 Animal.prototype。因此 cat.sayHello() 能运行,实际调用自 Animal.prototype。
  • Animal.prototype.constructor —— 指回 Animal 构造函数自身。
  • Animal.__proto__ —— 作为一个函数对象,它的原型指向了 Function.prototype,正因如此:“万物皆对象,函数也是对象”。
  • Animal.prototype.__proto__ —— Animal.prototype 的原型是 Object.prototype,意味着所有实例最终都会沿原型链归结到 Object.prototype。

关系图结构

cat  (实例对象)
  |
  v
cat.__proto__  -------->  Animal.prototype
                              |
                              v
                  Animal.prototype.__proto__  -->  Object.prototype

三、原型链究竟怎么查找?prototype__proto__ 在其中扮演什么角色?

查找规则

当你访问一个对象的属性,比如 cat.sayHello(),JS 引擎会:

  1. 先在实例(cat)自身查找属性。
  2. 没有则顺着 cat.__proto__ 也就是构造函数的 prototype 继续找。
  3. 没找到就沿着 Animal.prototype.__proto__(即 Object.prototype)再查找。
  4. 一直到原型链顶端 Object.prototype.__proto__ 为 null。

思考:如果中间任一层查到属性,就会停止;否则找不到就返回 undefined。


四、深挖原理:prototype 的作用

  1. 函数特有属性。普通对象没有 prototype,函数才有。
  2. 实现继承。所有实例共享 prototype 上的方法或属性。
  3. 被用来填充新实例的 __proto__
function Foo() {}
const f1 = new Foo();
const f2 = new Foo();
console.log(f1.__proto__ === Foo.prototype); // true
console.log(f2.__proto__ === Foo.prototype); // true

这正是原型“共享属性”的由来。


五、慎用 __proto__,你该怎么写才标准?

  • __proto__ 曾是大多数浏览器的非标准实现,但已在 ES6 标准化。
  • 更推荐的方式是用 Object.getPrototypeOf(obj)Object.setPrototypeOf(obj, proto) 读写原型。

例:

Object.getPrototypeOf(cat) === Animal.prototype; // true

六、常见误区大解析

  • 误区 1:可以直接 new 出一个 prototype
    错!prototype 只是对象模板,不能直接实例化。只能用构造函数 new 出对象实例。
  • 误区 2:以为所有对象都有 prototype
    只有函数有 prototype;普通对象没有。
  • 误区 3:误认为 prototype 和 proto 是一回事
    概念和用途都不相同,但通过实例和构造函数连接在一起,才构成原型链。

七、实际开发中你该怎么应用原型链?

  • 写继承

    function Animal() {}
    function Dog() {}
    Dog.prototype = new Animal();
    
  • 对象扩展

    const obj = { x: 1 };
    const newObj = Object.create(obj); // newObj.__proto__ === obj
    

八、面试直击·高频经典面试题

题目:请写出以下代码的执行结果,并解释原因!

function Parent() {}
Parent.prototype.say = function() { console.log('parent'); };
function Child() {}
Child.prototype = new Parent();
const c = new Child();
c.say();
console.log(c.__proto__ === Child.prototype); // ?
console.log(Child.prototype.__proto__ === Parent.prototype); // ?

答案:

  • c.say() 输出 'parent',因为继承自 Parent.prototype。
  • c.__proto__ === Child.prototype 为 true
  • Child.prototype.__proto__ === Parent.prototype 为 true

剖析: 正是原型链和 prototype、proto 的精准配合!


九、总结

  • prototype 连接构造函数和实例,使属性/方法共享和继承成为可能。
  • __proto__ 连接对象和其原型,形成灵活的原型链条。

如果你觉得这篇文章有用,记得点赞、收藏、分享,关注我查看更多前端干货!