一次讲透 JS 原型链 —— prototype、__proto__ 和 constructor 到底啥关系

原型链是 JS 面向对象的基石。3 个绕不开的问题:

  • prototype__proto__constructor 有啥区别?
  • new 一个对象时,原型链是怎么建的?
  • instanceof 的原理是什么?

先看一个现象:

js 复制代码
function Person() {}

Person.prototype.sayHi = function() {
  return 'hi';
};

const p = new Person();
console.log(p.sayHi());  // "hi"

p 上明明没定义 sayHi,却能调用。为什么?

答:原型链

一、原型链查找机制

访问对象属性时,JS 会这么找:

  1. 先在实例自身找
  2. 找不到 → 沿 __proto__ 往上找
  3. 一直找到 Object.prototype
  4. Object.prototype.__proto__ === null,链的尽头
  5. 还找不到 → 返回 undefined

对着上面的例子:

js 复制代码
function Person() {
  this.name = '张三';
}

Person.prototype.sayHi = function() {
  return 'hi';
};

const p = new Person();

console.log(p.name);      // "张三" ------ 自身找到
console.log(p.sayHi());   // "hi"    ------ 从 Person.prototype 找到
console.log(p.toString);  // Object.prototype 上的方法
console.log(p.abc);       // undefined ------ 找到 null 都没找到

查找路径:

javascript 复制代码
p → Person.prototype → Object.prototype → null

这就是"原型链" 。链上的每一环,靠 __proto__ 连起来。

二、3 个核心概念

理解了查找机制,再看这 3 个概念就清晰了。

概念 1:prototype(函数才有)

每个函数 都有一个 prototype 属性,它是一个对象

js 复制代码
function Person() {}

console.log(Person.prototype);
// { constructor: Person }

作用 :通过 new 创建的所有实例,共享 prototype 上的属性和方法

js 复制代码
Person.prototype.sayHi = function() {
  return 'hi';
};

const p1 = new Person();
const p2 = new Person();

p1.sayHi();  // "hi"
p2.sayHi();  // "hi"  ------ 两个实例共享同一个方法

为什么设计成这样?

省内存 ------ 100 个实例不用各存一份方法,都指向 prototype 上的同一个函数。

概念 2:__proto__(所有对象都有)

每个对象 都有 __proto__ 属性(现在推荐用 Object.getPrototypeOf())。

它指向创建这个对象的构造函数的 prototype

js 复制代码
function Person() {}
const p = new Person();

console.log(p.__proto__ === Person.prototype);  // true

关键 :__proto__ 就是上面查找机制里"往上找"用的那根线

概念 3:constructor(prototype 对象上的属性)

每个 prototype 对象 都有 constructor 属性,指回构造函数本身

js 复制代码
function Person() {}

console.log(Person.prototype.constructor === Person);  // true

作用:通过实例找回它的构造函数。

js 复制代码
const p = new Person();
console.log(p.__proto__.constructor === Person);  // true
console.log(p.constructor === Person);  // true(通过原型链找到)

三、三者关系图

javascript 复制代码
       Person(构造函数)
           │
           │ prototype
           ▼
    Person.prototype(原型对象)
        │        ▲
        │        │ constructor
        │________│
        │
        │ __proto__
        ▼
    Object.prototype
        │
        │ __proto__
        ▼
        null

用 new 创建实例后:

scss 复制代码
       Person(构造函数)
           │
           │ prototype
           ▼
    Person.prototype
        ▲
        │ __proto__
        │
        p(实例)  ← 通过 new 创建

核心公式:

  • 实例.__proto__ === 构造函数.prototype
  • 构造函数.prototype.constructor === 构造函数

四、new 时原型链是怎么建的

new Fn() 时,内部会做一个关键动作:

js 复制代码
const instance = Object.create(Fn.prototype);

Object.create(Fn.prototype) = 创建一个空对象,把它的 __proto__ 指向 Fn.prototype

这就是为什么 new 出来的实例能沿原型链访问 Fn.prototype 上的方法。

new 的完整过程不熟悉可以看下 new 都干了什么

五、instanceof 其实就是查原型链

理解了原型链查找,instanceof 就是顺手能想到的应用

a instanceof B 判断的是:沿着 a 的原型链往上找,看有没有一环等于 B.prototype

用之前的例子:

js 复制代码
function Person() {}
const p = new Person();

console.log(p instanceof Person);  // true
console.log(p instanceof Object);  // true

p 的原型链:

javascript 复制代码
p → Person.prototype → Object.prototype → null
  • Person.prototype 在这条链上 → p instanceof Person 为 true
  • Object.prototype 也在这条链上 → p instanceof Object 也为 true

理解了这个,手写 instanceof 就是把上面这个查找过程翻译成代码:

js 复制代码
function myInstanceof(instance, Constructor) {
  let proto = Object.getPrototypeOf(instance);  // 相当于 instance.__proto__
  
  while (proto) {
    if (proto === Constructor.prototype) {
      return true;
    }
    proto = Object.getPrototypeOf(proto);  // 沿原型链往上找
  }
  
  return false;
}

测试:

js 复制代码
console.log(myInstanceof(p, Person));  // true
console.log(myInstanceof(p, Object));  // true
console.log(myInstanceof(p, Array));   // false

六、几个常见误区

误区 1:Array.__proto__ === Object.prototype?

。Array 本身是一个函数(构造函数)。

js 复制代码
console.log(Array.__proto__ === Function.prototype);  // true
console.log(Array.prototype.__proto__ === Object.prototype);  // true

记忆:

  • 函数的 __proto__Function.prototype
  • 函数的 prototype.__proto__Object.prototype(因为 prototype 是普通对象)

误区 2:直接修改 prototype 会怎样?

js 复制代码
function Person() {}
const p1 = new Person();

Person.prototype = { sayHi: () => 'hi' };  // 直接替换 prototype

const p2 = new Person();

p1.sayHi();  // 报错 ------ p1 还指向旧的 prototype
p2.sayHi();  // "hi"  ------ p2 指向新 prototype

替换 prototype 不影响已创建的实例,只影响后续 new 的。

误区 3:__proto__ 是啥时候赋值的?

在 new 那一刻。之后修改构造函数的 prototype 引用,已存在的实例不受影响。

七、总结

3 个核心概念:

概念 属于谁 指向什么
prototype 函数 原型对象
proto 对象 构造函数的 prototype
constructor prototype 构造函数本身

2 条核心公式:

ini 复制代码
实例.__proto__ === 构造函数.prototype
构造函数.prototype.constructor === 构造函数

查找机制:沿 __proto__ 向上,到 null 结束。

相关推荐
高级程序源22 分钟前
django大学生创新创业项目管理系统94923-计算机课程设计、毕业设计
javascript·vue.js·spring boot·后端·python·django·课程设计
lizz22761 小时前
Babel插件手册
javascript
VXbishe4 小时前
【课程设计】基于SpringBoot的乡村政务服务系统的设计与实现-计算机毕设77011
javascript·vue.js·spring boot·python·php·课程设计·政务
志尊宝4 小时前
Vue3 零基础每日笔记(037):动手封装 useMouse 与 useWindowSize——事件监听的标准模板
前端·javascript·vue.js·笔记·html5
shmily麻瓜小菜鸡4 小时前
TDD(测试驱动开发)详解
开发语言·javascript·typescript·node.js·ecmascript
wcy03106 小时前
POST为什么会发送两次请求
javascript·ajax
志尊宝6 小时前
Vue3 零基础每日笔记(047):动态路由与路由参数——:id 传参、query 传参、props 解耦
前端·javascript·vue.js·笔记·html5
朝屯暮蒙vi10 小时前
随记:h5+中uploader不能添加参数数据,要通过task
javascript
泯泷18 小时前
那段文字是谁删的?Yjs 14 正式版之前,一套删除归属方案的实现与边界
前端·javascript·算法
hold?fish:palm19 小时前
34 合并K个升序链表
javascript·算法·链表