区分__proto__和prototype

彻底区分__proto__与prototype:从JS底层到Vue实战

要彻底理解 __proto__prototype,需穿透 JavaScript 原型链的底层逻辑,再结合 Vue 框架的实例体系落地应用。本文从「归属、作用、关联、误区、实战」五个核心维度拆解,补充大量案例、调试技巧和 Vue 实战场景,帮你吃透这两个高频易混概念。

一、核心定义与归属:先明确"谁拥有谁"

prototype__proto__ 是原型链的两大核心载体,但二者的归属、本质和目的截然不同,是理解原型链的第一道门槛。

概念 归属对象 本质/类型 存在的目的 典型示例
prototype 只有函数(构造函数) 普通对象(原型对象) 存放"供所有实例继承的公共属性/方法",是构造函数的"原型仓库" Function.prototypeArray.prototypeVue.prototype
__proto__ 所有对象(包括函数) 原型指针(内置访问器属性) 连接实例与原型对象,是实例的"原型导航",用于原型链查找(ES6 标准化,等价于 Object.getPrototypeOf() {}.__proto__[].__proto__vm.__proto__

关键结论(补充强化):

  1. 普通对象/数组/实例无 prototype
    比如 const obj = {}; console.log(obj.prototype)undefinedconst arr = []; console.log(arr.prototype)undefined;只有 function F() {} 这样的函数才有 F.prototype

  2. 函数"身兼两职"
    函数既是"可执行的代码块",也是"对象"------因此函数既有 prototype(作为构造函数的仓库),也有 __proto__(作为对象的导航指针,指向 Function.prototype)。

    javascript 复制代码
    function Foo() {}
    console.log(Foo.prototype); // { constructor: ƒ Foo(), __proto__: Object }(仓库)
    console.log(Foo.__proto__); // ƒ () { [native code] }(指向 Function.prototype)
  3. __proto__ 是访问器属性
    并非对象的直接属性,而是 Object.prototype 上的 get/set 方法,本质是调用 Object.getPrototypeOf()/Object.setPrototypeOf();早期是浏览器私有属性(如 __proto__ 曾写作 __proto__),ES6 才标准化。

二、核心作用:"仓库" vs "导航"

1. prototype:构造函数的"共享原型仓库"

prototype 是构造函数专属的"公共资源池",所有通过该构造函数创建的实例,都能共享仓库中的属性/方法------这是 JavaScript 实现"继承"和"内存优化"的核心手段。

原生 JS 深度示例(补充内存优化说明):
javascript 复制代码
// 构造函数:定义实例私有属性
function Person(name, age) {
  this.name = name; // 每个实例独有的私有属性
  this.age = age;
}

// 往 prototype 仓库添加公共方法(所有实例共享,仅占用一份内存)
Person.prototype.sayHi = function() {
  console.log(`Hi, 我是${this.name},${this.age}岁`);
};
Person.prototype.gender = '人类'; // 公共属性

// 创建多个实例
const p1 = new Person('张三', 20);
const p2 = new Person('李四', 22);

// 所有实例共享 prototype 中的方法/属性
console.log(p1.sayHi === p2.sayHi); // true(内存共享)
console.log(p1.gender === p2.gender); // true
p1.sayHi(); // Hi, 我是张三,20岁
p2.sayHi(); // Hi, 我是李四,22岁
核心特点(补充细节):
  • 内存优化核心 :如果每个实例都定义 sayHi 方法,1000 个实例会占用 1000 份内存;放在 prototype 中仅需 1 份,大幅节省内存。

  • constructor 反向指向prototype 自带 constructor 属性,默认指向所属构造函数,可用于判断实例的构造函数类型(但易被覆盖,需谨慎使用)。

    javascript 复制代码
    console.log(Person.prototype.constructor === Person); // true
    console.log(p1.constructor === Person); // true(p1 无 constructor,通过 __proto__ 查找)
    
    // 注意:若手动覆盖 prototype,constructor 会丢失
    Person.prototype = { sayHi: function() {} };
    console.log(p1.constructor === Person); // false(指向 Object)
    // 修复:手动重置 constructor
    Person.prototype.constructor = Person;
  • 原型属性可动态修改 :修改 prototype 后,所有未覆盖该属性的实例都会同步生效(原型链查找的动态性)。

    javascript 复制代码
    // 动态添加方法
    Person.prototype.run = function() { console.log(`${this.name}在跑步`); };
    p1.run(); // 张三在跑步(无需重新创建实例,直接生效)

2. __proto__:实例的"原型导航指针"

__proto__ 是每个对象内置的"导航器",指向创建该对象的构造函数的 prototype。当实例访问属性/方法时,遵循"先自身、后原型"的规则:

  1. 先查找实例自身是否有该属性/方法(如 p1.name);
  2. 若没有,通过 __proto__ 向上查找构造函数的 prototype(如 p1.sayHi);
  3. 若仍没有,继续通过 prototype.__proto__ 向上查找,直到 Object.prototype
  4. 若最终未找到,返回 undefined(方法则报错 xxx is not a function)。
原生 JS 深度示例(补充遮蔽效应):
javascript 复制代码
function Person(name) {
  this.name = name;
}
Person.prototype.sayHi = function() { console.log('原型方法'); };
Person.prototype.age = 18;

const p1 = new Person('张三');

// 1. 原型链查找:自身无 → __proto__ 找 prototype
console.log(p1.age); // 18(来自 Person.prototype)
p1.sayHi(); // 原型方法(来自 Person.prototype)

// 2. 遮蔽效应:实例属性覆盖原型属性
p1.age = 20; // 给实例添加自身属性
console.log(p1.age); // 20(自身属性优先)
console.log(p1.__proto__.age); // 18(原型属性仍存在)

// 3. 完整原型链查找路径
// p1.__proto__ → Person.prototype → Person.prototype.__proto__ → Object.prototype → null
console.log(p1.toString()); // [object Object](来自 Object.prototype)
console.log(p1.__proto__.__proto__.toString === Object.prototype.toString); // true
核心特点(补充细节):
  • "只读"的本质 :ES6 规范中 __proto__ 是"只读"的(严格模式下修改会报错),虽然部分浏览器允许修改,但会破坏原型链的稳定性,且触发引擎优化失效(性能下降)。

  • 原型链的终点 :所有原型链最终指向 Object.prototype.__proto__,其值为 null(无更高层原型)。

    javascript 复制代码
    console.log(Object.prototype.__proto__); // null
    console.log(p1.__proto__.__proto__.__proto__); // null

三、关联关系:__proto__ 是实例与 prototype 的桥梁

原型链的本质,是 __proto__ 把多个 prototype 串联起来的链式结构。我们用可视化文字链 + 完整验证代码 拆解核心关联:

1. 通用原型链结构(补充函数/数组的特例)

复制代码
// 普通实例的原型链
实例 p1 → __proto__ → Person.prototype → __proto__ → Object.prototype → __proto__ → null

// 函数的原型链(函数既是构造函数也是对象)
函数 Person → __proto__ → Function.prototype → __proto__ → Object.prototype → __proto__ → null

// 数组的原型链(数组是 Array 的实例)
数组 [1,2,3] → __proto__ → Array.prototype → __proto__ → Object.prototype → __proto__ → null

2. 完整验证代码(补充注释和解释)

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

// 1. 实例的 __proto__ === 构造函数的 prototype(核心关联)
console.log(p1.__proto__ === Person.prototype); // true

// 2. 构造函数(Person)作为对象,__proto__ 指向 Function.prototype
console.log(Person.__proto__ === Function.prototype); // true
// 补充:Function 自身的 __proto__ 指向自身(特例)
console.log(Function.__proto__ === Function.prototype); // true

// 3. 构造函数的 prototype 是普通对象,__proto__ 指向 Object.prototype
console.log(Person.prototype.__proto__ === Object.prototype); // true

// 4. Object.prototype 是原型链顶端,__proto__ 为 null
console.log(Object.prototype.__proto__); // null

// 5. 数组的原型链验证
const arr = [1,2,3];
console.log(arr.__proto__ === Array.prototype); // true
console.log(Array.prototype.__proto__ === Object.prototype); // true

3. 核心关联结论

  • __proto__ 是连接"实例"和"构造函数 prototype"的唯一桥梁;
  • 原型链查找的本质,是 __proto__ 不断向上指向不同 prototype 的过程;
  • 所有对象最终都继承自 Object.prototype(除了 Object.create(null) 创建的无原型对象)。

四、常见误区:彻底避坑(补充更多误区)

误区 1:"实例有 prototype 属性"

❌ 错误:const p1 = new Person(); console.log(p1.prototype);

✅ 正确:实例仅拥有 __proto__prototype 是构造函数的专属属性;p1.prototype 始终返回 undefined

误区 2:"proto 和 prototype 是同一个东西"

❌ 错误:混淆"指针"和"仓库",认为 p1.__proto__ === Person.prototype 就是"同一个";

✅ 正确:p1.__proto__指向 Person.prototype 的指针,二者是"引用关系"而非"同一对象";只有当手动修改 p1.__proto__ = {} 时,引用才会断开。

误区 3:"修改 proto 是常规操作"

❌ 错误:直接修改 __proto__(如 p1.__proto__ = { age: 20 });

✅ 正确:修改 __proto__ 会:

  1. 破坏原型链,导致实例丢失原有原型方法(如 p1.sayHi 失效);
  2. 触发 JavaScript 引擎的"去优化"(JIT 优化失效,性能下降 10 倍以上);
  3. 若需修改原型,优先操作构造函数的 prototype(如 Person.prototype.age = 20)。

误区 4:"constructor 属性永远可靠"

❌ 错误:认为 p1.constructor 一定指向创建它的构造函数;

✅ 正确:constructorprototype 上的普通属性,可被轻易覆盖:

javascript 复制代码
Person.prototype = { sayHi: function() {} }; // 覆盖 prototype,丢失 constructor
const p1 = new Person();
console.log(p1.constructor === Person); // false(指向 Object)

若需依赖 constructor,需手动重置:Person.prototype.constructor = Person

误区 5:"原型链查找无性能损耗"

❌ 错误:认为原型链查找和直接访问实例属性效率一致;

✅ 正确:原型链层级越深,查找耗时越长(虽现代引擎优化后差距极小,但深层原型链仍需避免);建议:

  1. 高频访问的属性/方法,优先定义在实例自身;
  2. 原型链层级控制在 3 层以内。

误区 6:"Object.create(null) 的对象有 proto"

❌ 错误:认为所有对象都有 __proto__

✅ 正确:Object.create(null) 创建的"纯净对象"无原型,因此无 __proto__

javascript 复制代码
const pureObj = Object.create(null);
console.log(pureObj.__proto__); // undefined
console.log(Object.getPrototypeOf(pureObj)); // null

五、实战进阶:原型链的实用技巧(新增章节)

1. 手动实现继承(基于 prototype)

这是面试高频考点,也是理解 prototype 核心价值的实战场景:

javascript 复制代码
// 父构造函数
function Parent(name) {
  this.name = name;
}
Parent.prototype.sayName = function() { console.log(this.name); };

// 子构造函数
function Child(name, age) {
  Parent.call(this, name); // 继承父类实例属性
  this.age = age;
}

// 核心:让子类 prototype 继承父类 prototype
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child; // 重置 constructor

// 子类专属方法
Child.prototype.sayAge = function() { console.log(this.age); };

// 测试
const child = new Child('小明', 10);
child.sayName(); // 小明(继承父类方法)
child.sayAge(); // 10(子类专属方法)
console.log(child instanceof Child); // true
console.log(child instanceof Parent); // true

2. 正确判断实例类型(instanceof vs proto

  • instanceof:判断实例的原型链中是否包含构造函数的 prototype(推荐);
  • __proto__:直接对比指针(易出错,不推荐)。
javascript 复制代码
console.log(child instanceof Child); // true(原型链包含 Child.prototype)
console.log(child.__proto__ === Child.prototype); // true(仅对比直接原型)
// 注意:instanceof 能识别多层继承,__proto__ 仅识别直接原型
console.log(child instanceof Parent); // true
console.log(child.__proto__ === Parent.prototype); // false

六、在 Vue 中的实际体现(强化+补充场景)

Vue 的实例体系(vm/根实例、vc/组件实例)完全基于原型链设计,__proto__prototype 是理解 Vue 实例能力继承的底层钥匙。

1. Vue 实例的核心方法继承(补充 Vue 2/3 对比)

Vue 的 $emit$watch$mount 等核心方法,本质是挂载在原型上的公共方法:

javascript 复制代码
// Vue 2 源码简化逻辑
function Vue(options) {
  this._init(options); // 初始化实例
}
Vue.prototype.$emit = function(eventName, ...args) {
  // 触发自定义事件的核心逻辑
  const listeners = this._events[eventName];
  if (listeners) {
    listeners.forEach(listener => listener.apply(this, args));
  }
};

// 创建 vm 根实例
const vm = new Vue({ el: '#app' });
// vm 通过 __proto__ 找到 Vue.prototype.$emit
console.log(vm.$emit === Vue.prototype.$emit); // true

// Vue 3 变化:弱化全局 Vue.prototype,改为应用实例级注入
import { createApp } from 'vue';
const app = createApp({});
// 全局方法挂载到应用实例的核心原型
app.config.globalProperties.$myMethod = function() {};
const rootVc = app.mount('#app');
// rootVc 通过 __proto__ 链式查找核心原型
console.log(rootVc.$myMethod); // ƒ () {}

2. 组件实例(vc)的原型链(补充调试技巧)

Vue 组件的构造函数是 VueComponent,其原型链设计保证了组件能继承 Vue 核心能力:

javascript 复制代码
// Vue 内部简化逻辑(Vue 2)
function VueComponent(options) {
  Vue.call(this, options); // 调用 Vue 构造函数,继承基础属性
}
// 核心:让组件原型继承 Vue.prototype
VueComponent.prototype.__proto__ = Vue.prototype;

// 渲染组件时,Vue 自动创建 vc 实例
const vc = new VueComponent();
// vc 的原型链:vc.__proto__ → VueComponent.prototype → __proto__ → Vue.prototype
console.log(vc.$emit === Vue.prototype.$emit); // true
调试技巧:在浏览器开发者工具查看原型链
  1. 打开 Vue Devtools → 选中任意组件 → 切换到"Console";
  2. 输入 $vm0(当前组件实例 vc)→ 展开 __proto__ 可看到 VueComponent.prototype
  3. 再展开 __proto__ 可看到 Vue.prototype,能直接查看 $emit$watch 等方法。

3. 组件 data 必须是函数的原型链原因(补充原理)

  • 若组件 data 是对象,会挂载到 VueComponent.prototype 上,导致所有 vc 实例共享同一 data(数据污染);

  • 要求 data 是函数,每次创建 vc 时执行函数返回新对象,保证每个实例的 data 是独立私有属性:

    javascript 复制代码
    // 错误示例:data 为对象(所有 vc 共享)
    Vue.component('BadComp', {
      data: { count: 0 }, // 挂载到 VueComponent.prototype
      template: '<button @click="count++">{{ count }}</button>'
    });
    // 多次使用 <BadComp>,点击一个按钮,所有按钮的 count 都会增加(数据共享)
    
    // 正确示例:data 为函数(每个 vc 独立)
    Vue.component('GoodComp', {
      data() { return { count: 0 }; }, // 每次创建 vc 都返回新对象
      template: '<button @click="count++">{{ count }}</button>'
    });

4. Vue Mixin 与原型链的关系(新增场景)

Vue Mixin 的本质是修改组件的 VueComponent.prototype,让 vc 实例通过 __proto__ 优先查找 Mixin 中的方法:

javascript 复制代码
// 定义 Mixin
const myMixin = {
  methods: { mixinMethod() { console.log('Mixin 方法'); } }
};
// 全局注册 Mixin
Vue.mixin(myMixin);

// 组件实例 vc 访问 Mixin 方法:vc.__proto__ → VueComponent.prototype → mixinMethod
const vc = vm.$refs.myComp;
vc.mixinMethod(); // Mixin 方法

5. Vue 3 setup 中访问原型属性(新增场景)

Vue 3 <script setup> 中无 this,需通过 getCurrentInstance 获取实例,再访问原型属性:

vue 复制代码
<script setup>
import { getCurrentInstance, onMounted } from 'vue';

onMounted(() => {
  const instance = getCurrentInstance();
  // 访问原型上的全局属性 $api
  const $api = instance.proxy.$api; 
  // 等价于:instance.vnode.componentInstance.__proto__.__proto__.$api
  $api.getList();
});
</script>

七、总结:核心区分表(补充对比维度)

维度 prototype __proto__
归属 仅函数(构造函数)拥有 所有对象(实例、函数、普通对象)拥有
本质 原型对象(仓库) 原型指针(导航器,访问器属性)
作用 存放供实例继承的公共属性/方法 连接实例与原型对象,实现原型链查找
可修改性 推荐修改(扩展原型、实现继承) 不推荐修改(破坏原型链、性能下降)
关联 实例.proto 指向构造函数.prototype 是实例访问 prototype 的唯一途径
内存特性 共享内存(所有实例复用) 仅占用指针内存(无额外开销)
Vue 中的体现 Vue.prototype 存放核心方法;VueComponent.prototype 存放组件方法 vm.proto 指向 Vue.prototype;vc.proto 指向 VueComponent.prototype
示例 Person.prototype.sayHi = function(){} p1.__proto__ === Person.prototype

最终记忆口诀:

  • prototype 是构造函数的"仓库",存公共方法,省内存;
  • __proto__ 是实例的"导航",找仓库内容,构链条;
  • 原型链 = __proto__ 串起的所有 prototype
  • Vue 实例的核心能力,都是 __proto__Vue.prototype 仓库"取"来的。

学习价值:

掌握 __proto__prototype 的区别与关联,不仅能:

  1. 吃透 JavaScript 面向对象的底层原理(继承、原型链);
  2. 理解 Vue 实例的能力继承逻辑(如 $emit 为何能全局调用);
  3. 规避原型链相关的开发陷阱(如组件 data 类型错误、修改 __proto__ 导致的 bug);
  4. 应对前端面试中的高频原型链问题(如手动实现继承、instanceof 原理)。
相关推荐
weixin_420947644 小时前
php composer update 指定包的分支非tag
开发语言·php·composer
Java.熵减码农4 小时前
基于VueCli自定义创建项目
前端·javascript·vue.js
一水鉴天4 小时前
整体设计 定稿 之6 完整设计文档讨论及定稿 之4 整体设计数据库设计规范(含两个版本)
开发语言·人工智能·架构
追逐梦想之路_随笔4 小时前
Js使用多线程Worker和单线程异步处理数据时间比较
前端·javascript
史上最菜开发4 小时前
Ant Design Vue V1.7.8版本,a-input 去空格
javascript·vue.js·anti-design-vue
光算科技4 小时前
商品颜色/尺码选项太多|谷歌爬虫不收录怎么办
java·javascript·爬虫
前端不太难4 小时前
Vue Router 权限系统设计实战
前端·javascript·vue.js
Evand J5 小时前
【EKF定位滤波例程】三维空间(XYZ)速度与位置观测与滤波(使用扩展卡尔曼滤波EKF),状态量和观测量都是非线性的。附MATLAB例程下载链接
开发语言·matlab
Aevget5 小时前
可视化工具LightningChart JS v8.1 重磅更新:热力图与 3D 可视化能力双提升!
javascript·3d·信息可视化·数据可视化·lightningchart