在JavaScript中,原型链是对象之间实现属性与方法共享的核心机制。与基于类的语言不同,JavaScript采用基于原型的继承,每一个对象都有一个内部属性指向它的原型,当访问某个成员时,如果自身不存在,就会顺着这条隐式链接向上查找。这种结构不是通过复制,而是通过委托来完成复用。

原型与原型对象的底层结构
每个函数在被创建时,都会自动获得一个prototype属性,这个属性指向一个普通对象,也就是我们常说的原型对象。构造函数通过new操作符生成实例时,实例内部会有一个隐式引用(在多数浏览器中表现为__proto__)指向构造函数的prototype。这意味着所有由同一个构造函数创建的实例,共享同一块原型内存,方法定义在原型上就能被全部实例访问,而不必在每个实例里都复制一份。
我们可以用一段简单代码观察这种关系。下面定义了一个构造函数Person,并在其原型上挂载了sayHi方法,随后创建实例验证原型指向。
function Person(name) {
this.name = name;
}
Person.prototype.sayHi = function() {
return 'Hi, ' + this.name;
};
var p = new Person('Tom');
console.log(p.__proto__ === Person.prototype); // true
console.log(p.sayHi()); // Hi, Tom
从内存角度看,p自身只保存了name属性,sayHi并不在p里,而是通过__proto__找到Person.prototype才拿到。如果直接在p上添加同名方法,就会形成属性遮蔽,原型上的方法不会被修改,只是被实例自身覆盖。这种结构带来了极低的内存开销,也解释了为何大规模对象系统中原型方法非常常见。
属性查找规则与链式继承逻辑
当我们读取obj.prop时,JavaScript引擎首先看obj自身是否有该属性;如果没有,就访问obj.__proto__,再看其原型;若仍没有,继续向上,直到抵达Object.prototype。一旦在Object.prototype上也找不到,就会返回undefined。这条由__proto__串联起来的路径就是原型链。它本质上是一种委托链,而非传统语言里的复制式继承。
基于这种规则,我们可以手动构造继承。一种经典写法是让子构造函数的原型指向父构造函数的实例,从而把两条原型链接起来。下面代码展示了如何用这种方式实现Student继承Person。
function Student(name, grade) {
Person.call(this, name);
this.grade = grade;
}
Student.prototype = new Person();
Student.prototype.constructor = Student;
Student.prototype.showGrade = function() {
return this.name + ' is in grade ' + this.grade;
};
var s = new Student('Lucy', 3);
console.log(s.sayHi()); // Hi, Lucy
console.log(s.showGrade()); // Lucy is in grade 3
上述代码中,Student.prototype变成了Person的实例,因此s在自身找不到sayHi时,会经过Student.prototype再找到Person.prototype。这种链式的查找保证了方法复用。同时要注意constructor的修正,否则s.constructor会错误地指向Person,影响后续类型判断与代码可读性。
使用instanceof判断类型时,底层就是沿着原型链检查构造函数的prototype是否出现在对象的链上。所以s instanceof Person为true,因为Person.prototype确实在s的原型链中。理解这一点,就不会再把instanceof简单看作类关系,而明白它是原型连通性的检测。
现代语法下的原型继承与实践建议
ES6引入了class关键字,让写法更接近传统面向对象语言,但底层依然是基于原型的机制。class只是语法糖,通过extends实现的继承,在引擎中仍靠__proto__与原型对象串联。使用class能减少手动绑定原型的错误,例如不再需要显式写Student.prototype = new Person(),而是由编译器处理原型连接。
下面用class改写前面的例子,逻辑完全一致,但结构更清晰,也避免了constructor指向错乱的问题。
class Person {
constructor(name) {
this.name = name;
}
sayHi() {
return 'Hi, ' + this.name;
}
}
class Student extends Person {
constructor(name, grade) {
super(name);
this.grade = grade;
}
showGrade() {
return this.name + ' is in grade ' + this.grade;
}
}
const s2 = new Student('Bob', 5);
console.log(s2.sayHi());
console.log(s2.showGrade());
在实际项目中,建议把共用方法放在原型或class的方法定义中,而不是在构造函数体内用this.method = function(){}的方式,否则每个实例都会创建新的函数副本,失去原型共享的意义。对于需要私有状态的数据,再放到构造函数里用this绑定。此外,当对象层级很深时,过长的原型链会带来轻微的查找成本,但比起内存浪费,这通常可以接受。
理解基于原型的继承,关键是放弃复制思维,接受委托思维。对象不是从父级拷贝成员,而是在需要时顺着链去借。掌握了原型链,你就能准确预测任意属性的来源、解释方法覆盖现象,也能在调试时快速定位为什么某个属性读到了意料之外的值。这是深入JavaScript无法绕开的一块基石。
javascriptprototype原型链修改时间:2026-08-16 10:54:13