陌上人如玉
公子世无双

一文搞懂JavaScript原型链:从入门到通透

JavaScript的原型链是前端入门的核心难点,也是理解JS面向对象、继承机制的关键。本文用通俗的比喻+可运行的代码示例,从基础概念到完整链路,一步步拆解原型链的本质,新手也能轻松理解。

一、先搞懂3个核心基础概念

在聊原型链之前,必须先理清「原型」「prototype」「proto」这三个高频概念,它们是原型链的基石。

1. 万物皆对象,函数有特殊性

JS中几乎所有东西都是对象(null/undefined等原始值除外),而函数是一种特殊的对象——它既有普通对象的特性,又有专属的prototype属性。

2. prototype:构造函数的“共享仓库”

• 归属:只有函数(箭头函数除外)才有prototype属性;

• 指向:指向一个「原型对象」;

• 作用:存放所有实例共享的属性/方法,避免每个实例重复创建相同方法,节省内存。

原型对象默认有一个constructor属性,反向指向创建它的构造函数:

// 构造函数
function Person(name) {
  this.name = name; // 实例私有属性(每个实例独有)
}

// 给原型对象添加共享方法
Person.prototype.sayHi = function() {
  console.log(`你好,我是${this.name}`);
};

// 验证constructor反向指向
console.log(Person.prototype.constructor === Person); // true

3. proto:原型链的“导航指针”

可以把它理解为对象的「原型导航箭头」,这是原型链的核心物理连接:
• 归属:几乎所有对象(实例、原型对象、函数对象)都有__proto__(ES6推荐用Object.getPrototypeOf()替代);

• 指向:固定指向该对象的「直接原型对象」(即创建它的构造函数的prototype);

• 作用:JS引擎通过这个指针,实现属性/方法的向上查找。

二、原型链的本质:一条“查找链”

原型链的核心是:通过__proto__指针串联起来的对象链,本质是实现「属性/方法的继承和共享」。

1. 原型链的查找规则

当访问对象的某个属性/方法时,JS会按以下顺序查找:
1. 先在对象自身查找,找到则返回;
2. 若没找到,通过__proto__找它的原型对象,继续查找;
3. 若原型对象也没有,继续通过原型对象的__proto__向上找;
4. 直到找到Object.prototype(顶层原型),若还没找到则返回undefined
5. 若查找的是方法且全程没找到,会报错xxx is not a function

2. 基础示例:验证原型链指向

下面的代码可直接在浏览器控制台/Node.js运行,直观看到原型链的层级:

// 1. 定义构造函数
function Person(name) {
  this.name = name;
}
Person.prototype.sayHi = function() {
  console.log(`你好,我是${this.name}`);
};

// 2. 创建实例
const p1 = new Person("张三");

// 3. 核心指针指向验证
console.log("p1.__proto__ → Person.prototype:", p1.__proto__ === Person.prototype); // true
console.log("Person.prototype.__proto__ → Object.prototype:", Person.prototype.__proto__ === Object.prototype); // true
console.log("Object.prototype.__proto__ → null:", Object.prototype.__proto__); // null(原型链终点)

3. 原型链的完整结构(以Person实例为例)

用层级展示更清晰:

p1(实例对象)
  ↓ __proto__
Person.prototype(原型对象)
  ↓ __proto__
Object.prototype(顶层原型对象)
  ↓ __proto__
null(原型链终点)

三、容易忽略的关键:函数对象的原型链

前面的例子只讲了「实例对象」的原型链,而「函数对象」(如Person、Object)的原型链是另一条关键分支,也是面试/学习中的高频易错点。

1. 函数对象的proto指向

所有函数(包括自定义函数、内置构造函数)都是Function的实例,因此:
• 自定义函数(如Person)的__proto__Function.prototype

• 内置构造函数(如Object/Array)的__proto__Function.prototype

• 最特殊的FunctionFunction.__proto__Function.prototype(自己是自己的实例)。

2. 完整验证代码(覆盖所有核心指向)

// 自定义构造函数
function Person(name) {
  this.name = name;
}
Person.prototype.sayHi = function() {
  console.log(`你好,我是${this.name}`);
};
const p1 = new Person("张三");

// 1. 实例对象的原型链
console.log("p1.__proto__ → Person.prototype:", p1.__proto__ === Person.prototype); // true

// 2. 自定义函数的原型链
console.log("Person.__proto__ → Function.prototype:", Person.__proto__ === Function.prototype); // true

// 3. 内置构造函数的原型链
console.log("Object.__proto__ → Function.prototype:", Object.__proto__ === Function.prototype); // true
console.log("Array.__proto__ → Function.prototype:", Array.__proto__ === Function.prototype); // true

// 4. 顶层原型的终点
console.log("Function.prototype.__proto__ → Object.prototype:", Function.prototype.__proto__ === Object.prototype); // true
console.log("Object.prototype.__proto__ → null:", Object.prototype.__proto__); // null

p1.sayHi(); // 你好,我是张三

3. 核心链路总结(补全所有分支)

// 实例对象分支
p1 → Person.prototype → Object.prototype → null

// 函数对象分支
Person/Object/Array → Function.prototype → Object.prototype → null
Function → Function.prototype → Object.prototype → null

四、原型链的实用价值

1. 实现方法共享(节省内存)

如果每个实例都定义相同方法,会重复占用内存;把方法放在原型上,所有实例共享一份:

// 不好的写法:每个实例都创建新函数
function PersonBad(name) {
  this.name = name;
  this.sayHi = function() { console.log(this.name) };
}
const p2 = new PersonBad("李四");
const p3 = new PersonBad("王五");
console.log(p2.sayHi === p3.sayHi); // false(不同函数)

// 好的写法:方法放在原型上
function PersonGood(name) {
  this.name = name;
}
PersonGood.prototype.sayHi = function() { console.log(this.name) };
const p4 = new PersonGood("李四");
const p5 = new PersonGood("王五");
console.log(p4.sayHi === p5.sayHi); // true(同一个函数)

2. 实现ES5继承(核心原理)

原型链是ES5实现继承的核心,通过修改子类原型的__proto__指向父类原型:

// 父类
function Parent(name) {
  this.name = name;
}
Parent.prototype.getName = function() {
  return this.name;
};

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

// 继承原型方法
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child; // 修复constructor指向

// 子类新增方法
Child.prototype.getAge = function() {
  return this.age;
};

const child = new Child("小明", 10);
console.log(child.getName()); // 小明(继承父类方法)
console.log(child.getAge()); // 10(子类自身方法)

五、常见易错点&避坑指南

1. 修改原型对象要修复constructor:直接替换原型对象会丢失constructor,需手动指向原构造函数:

   Person.prototype = {
     constructor: Person, // 手动修复
     sayHi: function() {}
   };

2. 区分proto和prototype:

记住「对象有proto,函数有prototype」,实例的proto = 构造函数的prototype;

3. 箭头函数无prototype:

箭头函数不能作为构造函数,也没有prototype属性;

4. Object.prototype是最终终点:

所有原型链最终都会走到Object.prototype,它的proto是null。

六、核心知识点总结

1. 原型链的核心:

__proto__指针,串联起对象的原型层级,实现属性/方法的继承和查找;

2. 查找规则:

自身 → 直接原型 → 顶层原型(Object.prototype)→ null;

3. 函数对象的特殊指向:

所有函数的proto指向Function.prototype,Function自身的proto指向自己的prototype;

4. 原型链的价值:

实现方法共享(节省内存)、支撑ES5继承,是JS面向对象的基础。

掌握原型链的核心逻辑,不仅能理解JS的继承机制,也能更清晰地排查“属性找不到”“this指向异常”等常见问题,为后续学习框架源码、高级JS特性打下基础。

赞(0) 打赏
未经允许不得转载:陌上寒 » 一文搞懂JavaScript原型链:从入门到通透

评论 抢沙发

觉得文章有用就打赏一下文章作者

非常感谢你的打赏,我们将继续给力更多优质内容,让我们一起创建更加美好的网络世界!

微信扫一扫

支付宝扫一扫