JavaScript 原型系统核心解析
引言:JS 面向对象的独特哲学
在传统的面向对象语言中,对象是基于 "类"(Class) 创建的
- 类是模板,定义了属性和方法;
- 对象是类的实例 ,通过
new Class()生成; - 继承是"血缘关系"------子类继承父类,结构静态且明确。
而 JavaScript 采用 原型式面向对象(Prototype-based OOP) :
- 没有"类"的概念 (ES6 的
class只是语法糖); - 对象直接从 另一个对象(原型) 继承属性和方法;
- 每个对象都有一个内部链接(
[[Prototype]]),指向它的原型; - 继承是"委托关系"------找不到属性时,自动向原型查找。
构造函数与实例创建
js
function Person(name,age){
this.name =name;
this.age =age;
}
Person.prototype.speci = '人类'
- new 的作用:创建一个新的对象,并返回
this的指向:this会在new这个新对象时绑定到新创建的对象上- 实例私有属性的定义:在 JavaScript 中,实例私有属性是指直接定义在对象自身上、不被原型共享的属性。在上面的代码中,name和age是私有的,而speci则是所有对象实例所共有的属性
对象字面量的创建就像是传统工匠
构造函数就像是一个模具,可以快速的创建的出相同的对象实例
prototype 是什么?

- 定义:他是所有函数独有的一个特殊属性
- 作用:用来存储所有实例对象所共有的属性和方法
- 结构:默认包含
constructor,指向该实例原型的构造函数,__proto__指向对象原型
js
{
constructor:...
__proto__:....
}
实例如何访问原型? __ proto __ 与原型链
js
function Person(name,age){
this.name =name;
this.age =age;
}
Person.prototype.speci = '人类'
const person1 =new Person('张三',18);
const person2 =new Person('李四',19);
console.log(person1);
console.log(person1.__proto__);
- __ proto__是什么 :是 JavaScript 中每个对象都具有的一个内部属性 ,它指向该对象的原型(prototype) ,也就是这个对象"从哪里继承属性和方法"。 等我们通过__proto__去进行访问时,可以得到该实例对象所继承的原型

但是,值得注意的是__proto__在现在并不推荐使用,
__proto__是[[Prototype]]的 getter/setter。 而在现在的编程当中,我们更推荐使用Object.getPrototypeOf和Object.setPrototypeOf来取代__proto__去 get/set 原型
prototype 与 __proto__的本质区别
- 归属的对象不同:prototype是只有函数才具有的属性,而__proto__是所有对象都具有的一个内部属性
- 所代表的含义不同:prototype是对象的原型,它包含了该构造函数所有实例对象的共有属性和方法,是一个对象,而__proto__ 是指向实例对象原型的链路,指向了实例对象的构造函数的prototype
- 二者的关系 :如图,实例.proto ==构造函数.prototype

覆盖 prototype 的常见问题
constructor丢失 考虑下面这段代码:
js
function Person(name,age){
this.name =name;
this.age =age;
}
Person.prototype={
speci:'人类',
}
var person1 =new Person('张三',18);
console.log(Person.prototype.constructor == Person);
打印的结果是什么 ? 你以为是ture吗?实际上却是false!!!
按照一贯的思维来说,Person对象原型的构造函数确实是Person(),但是观察上述代码,我们修改了prototype,并把他赋值了一个普通的对象,但是我们没有重写构造函数 ,这就导致了实际上person.protope已经变成了Object的原型,所以才会导致false的结果
- 如何正确重写并修复
-
- 方案一:重写构造函数
在修改prototype时在代码中添加constructor:Person;就能重新告诉引擎当前这个实例的原型依旧是Person
- 方案一:重写构造函数
-
- 方案二:只添加,不覆盖
例如:
- 方案二:只添加,不覆盖
ini
Person.prototype.speci='人类';
这样的操作只会在原有的基础上扩展出speci属性,而不会覆盖掉整个的prototype
函数也是对象:Function 与 Object 的关系
Person.__proto__ === Function.prototype
考虑下面这段代码:
js
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype = {
speci:"人类",
}
// 对象的原型对象是?
console.log(Object.getPrototypeOf(Person)==Person.prototype);//flase
我的理解 :
Person是一个构造函数(所以他是Function的实例),它的__proto__指向的是函数的的原型,也就是Function.prototype
Function则是Object的一个实例,它的__proto__指向了Object.prototype
- 原型链的完整路径
javascript
Person
└─ __proto__ → Function.prototype
└─ __proto__ → Object.prototype
└─ __proto__ → null
原型链的顶点:Object.prototype
所有对象都沿着原型链最终指向Object.prototype,而Object[[Prototype]]指向的null,这意味着它是所有对象的终点 
理解 JS 原型的关键要点
终极记忆法:
prototype是父亲给后代的实际财富,它代表着所有后代所实际拥有的共有属性和方法
__proto__更像是一份DNA的鉴定,它指明了实例对象的父亲或者说祖宗是谁,我继承的谁的财富
虽然在现代的javaScript我们可以使用class语法,但它实际上只是有一个语法糖,它的底层原理依然是构造函数 + prototype,清晰的理解prototype和__proto__有助于后面更高效的学习JS