彻底区分__proto__与prototype:从JS底层到Vue实战
要彻底理解 __proto__ 和 prototype,需穿透 JavaScript 原型链的底层逻辑,再结合 Vue 框架的实例体系落地应用。本文从「归属、作用、关联、误区、实战」五个核心维度拆解,补充大量案例、调试技巧和 Vue 实战场景,帮你吃透这两个高频易混概念。
一、核心定义与归属:先明确"谁拥有谁"
prototype 和 __proto__ 是原型链的两大核心载体,但二者的归属、本质和目的截然不同,是理解原型链的第一道门槛。
| 概念 | 归属对象 | 本质/类型 | 存在的目的 | 典型示例 |
|---|---|---|---|---|
prototype |
只有函数(构造函数) | 普通对象(原型对象) | 存放"供所有实例继承的公共属性/方法",是构造函数的"原型仓库" | Function.prototype、Array.prototype、Vue.prototype |
__proto__ |
所有对象(包括函数) | 原型指针(内置访问器属性) | 连接实例与原型对象,是实例的"原型导航",用于原型链查找(ES6 标准化,等价于 Object.getPrototypeOf()) |
{}.__proto__、[].__proto__、vm.__proto__ |
关键结论(补充强化):
-
普通对象/数组/实例无
prototype:
比如const obj = {}; console.log(obj.prototype)→undefined;const arr = []; console.log(arr.prototype)→undefined;只有function F() {}这样的函数才有F.prototype。 -
函数"身兼两职" :
函数既是"可执行的代码块",也是"对象"------因此函数既有prototype(作为构造函数的仓库),也有__proto__(作为对象的导航指针,指向Function.prototype)。javascriptfunction Foo() {} console.log(Foo.prototype); // { constructor: ƒ Foo(), __proto__: Object }(仓库) console.log(Foo.__proto__); // ƒ () { [native code] }(指向 Function.prototype) -
__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属性,默认指向所属构造函数,可用于判断实例的构造函数类型(但易被覆盖,需谨慎使用)。javascriptconsole.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。当实例访问属性/方法时,遵循"先自身、后原型"的规则:
- 先查找实例自身是否有该属性/方法(如
p1.name); - 若没有,通过
__proto__向上查找构造函数的prototype(如p1.sayHi); - 若仍没有,继续通过
prototype.__proto__向上查找,直到Object.prototype; - 若最终未找到,返回
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(无更高层原型)。javascriptconsole.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__ 会:
- 破坏原型链,导致实例丢失原有原型方法(如
p1.sayHi失效); - 触发 JavaScript 引擎的"去优化"(JIT 优化失效,性能下降 10 倍以上);
- 若需修改原型,优先操作构造函数的
prototype(如Person.prototype.age = 20)。
误区 4:"constructor 属性永远可靠"
❌ 错误:认为 p1.constructor 一定指向创建它的构造函数;
✅ 正确:constructor 是 prototype 上的普通属性,可被轻易覆盖:
javascript
Person.prototype = { sayHi: function() {} }; // 覆盖 prototype,丢失 constructor
const p1 = new Person();
console.log(p1.constructor === Person); // false(指向 Object)
若需依赖 constructor,需手动重置:Person.prototype.constructor = Person。
误区 5:"原型链查找无性能损耗"
❌ 错误:认为原型链查找和直接访问实例属性效率一致;
✅ 正确:原型链层级越深,查找耗时越长(虽现代引擎优化后差距极小,但深层原型链仍需避免);建议:
- 高频访问的属性/方法,优先定义在实例自身;
- 原型链层级控制在 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
调试技巧:在浏览器开发者工具查看原型链
- 打开 Vue Devtools → 选中任意组件 → 切换到"Console";
- 输入
$vm0(当前组件实例 vc)→ 展开__proto__可看到VueComponent.prototype; - 再展开
__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 的区别与关联,不仅能:
- 吃透 JavaScript 面向对象的底层原理(继承、原型链);
- 理解 Vue 实例的能力继承逻辑(如
$emit为何能全局调用); - 规避原型链相关的开发陷阱(如组件 data 类型错误、修改
__proto__导致的 bug); - 应对前端面试中的高频原型链问题(如手动实现继承、instanceof 原理)。