原型链与构造函数
七崽爱吃小饼干2025/12/29阅读 1
一、核心概念
构造函数(Constructor)、原型对象(Prototype)、__proto__属性是本节要讲解的三大重要概念。
以下是我们通过函数构造一个对象的过程,你有想过构造得到的实例对象的属性以及方法是怎么访问的?
js
function Person(name, age) {
this.name = name;
this.age = age;
}
// 将共享方法挂载到构造函数的 prototype(原型对象)上
Person.prototype.sayHi = function() {
console.log(`你好,我是${this.name},今年${this.age}岁`);
};
const person1 = new Person("张三", 20);
const person2 = new Person("李四", 22);
// 两个实例共享同一个 sayHi 方法
console.log(person1.sayHi === person2.sayHi); // true
person1.sayHi(); // 你好,我是张三,今年20岁
person2.sayHi(); // 你好,我是李四,今年22岁
// 原型对象的 constructor 属性指向构造函数
console.log(Person.prototype.constructor === Person); // true
1. 构造函数(Constructor)
构造函数是 用于创建和初始化特定类型对象的普通函数,本质上和普通函数没有语法区别,关键在于使用方式和命名约定:
- 命名约定:首字母大写(区分普通函数,如 Person、User)
- 调用方式:必须使用 new 关键字调用,否则会变成普通函数执行
- 核心作用:创建实例对象,并为实例初始化属性和方法
js
// 定义构造函数
function Person(name, age) {
// 实例属性(每个实例独立拥有)
this.name = name;
this.age = age;
// 实例方法(不推荐这样定义,会造成内存浪费)
this.sayHi = function() {
console.log(`你好,我是${this.name}`);
};
}
// 使用 new 关键字调用构造函数,创建实例
const person1 = new Person("张三", 20);
const person2 = new Person("李四", 22);
console.log(person1.name); // 张三
person2.sayHi(); // 你好,我是李四
2. 原型对象(Prototype)
每个函数(尤其是构造函数)都有一个 prototype 属性,这个属性指向一个对象,即「原型对象」。
- 原型对象的核心作用:原型对象也是一个普通的js对象,其存放所有实例共享的属性和方法,避免每个实例重复创建相同方法,节省内存
- 所有通过该构造函数创建的实例,都会共享原型对象上的属性和方法
- 原型对象默认包含一个 constructor 属性,指向其对应的构造函数(形成闭环)
- 实例本身并不具有prototype,需要通过__proto__去访问其构造函数的prototype
js
function Person(name, age) {
this.name = name;
this.age = age;
}
// 将共享方法挂载到构造函数的 prototype(原型对象)上
Person.prototype.sayHi = function() {
console.log(`你好,我是${this.name},今年${this.age}岁`);
};
const person1 = new Person("张三", 20);
const person2 = new Person("李四", 22);
// 两个实例共享同一个 sayHi 方法
console.log(person1.sayHi === person2.sayHi); // true
person1.sayHi(); // 你好,我是张三,今年20岁
person2.sayHi(); // 你好,我是李四,今年22岁
// 原型对象的 constructor 属性指向构造函数
console.log(Person.prototype.constructor === Person); // true
3.proto
每个 JavaScript 对象(除 null 外)都有一个 proto 隐藏属性(ES6 标准化后推荐使用 Object.getPrototypeOf() 访问),它的核心作用是:
- 指向创建该对象的构造函数的原型对象(prototype)
- 这是实例对象能够访问到构造函数原型上属性 / 方法的根本原因 proto 与 prototype 的关联
js
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHi = function() {
console.log(`你好,我是${this.name}`);
};
const person1 = new Person("张三", 20);
// 实例的 __proto__ 指向构造函数的 prototype
console.log(person1.__proto__ === Person.prototype); // true
console.log(Object.getPrototypeOf(person1) === Person.prototype); // true(推荐用法)
// 实例通过 __proto__ 访问原型上的方法
person1.__proto__.sayHi.call(person1); // 你好,我是张三(等价于 person1.sayHi())
二、原型链
1.原型链的本质
原型链是 由 proto 属性串联起来的对象链,它是 JavaScript 实现继承的核心机制。
2.原型链的查找规则
当访问一个对象的属性或方法时,JS 会按照以下顺序查找:
- 首先在 对象自身 上查找,如果找到则直接返回 / 执行
- 如果自身没有,就通过 proto 查找 其原型对象(构造函数的 prototype)
- 如果原型对象上也没有,继续通过原型对象的 proto 查找 原型的原型
- 以此类推,直到找到该属性 / 方法,或者查找到原型链的末端(也就是说,原型链是从实例自身开始逐级向上查找,如果有同名属性,那么会以先找到的为准。)
- 原型链的末端是 Object.prototype,它的 proto 指向 null(没有更上层的原型)
- 如果整个原型链都没有找到该属性 / 方法,最终返回 undefined(方法调用则会报错 xxx is not a function)
原型链查找示例:
js
function Person(name, age) {
this.name = name; // 实例自身属性
this.age = age;
}
// 原型对象上的属性/方法
Person.prototype.gender = "男";
Person.prototype.sayHi = function() {
console.log(`你好,我是${this.name}`);
};
// Object.prototype 上的方法(所有对象都能访问)
// Object.prototype.toString = function() { ... }
const person1 = new Person("张三", 20);
// 1. 查找自身属性:name
console.log(person1.name); // 张三(自身找到)
// 2. 查找原型属性:gender
console.log(person1.gender); // 男(自身没有,原型上找到)
// 3. 查找原型的原型(Object.prototype)上的方法:toString
console.log(person1.toString()); // [object Object](Person.prototype 没有,Object.prototype 上找到)
// 4. 查找不存在的属性:height
console.log(person1.height); // undefined(整个原型链都没有)
假设我们在浏览器打印person1
观察结果我们可以发现:
age以及name等通过this声明的属性作为实例属性挂载在实例对象本身。- 通过
__proto__直接访问到了其构造函数Person的Prototype。 Person的Prototype挂载了原型方法与原型属性,这是所有通过Person构造的实例公用的。Person的Prototype还挂载了其自身,也就是constructor: PersonPerson的Prototype也有__proto__,通过该属性可以访问到更上一层的原型对象也就是Object的Prototype,可以看到Object的Prototype拥有toString等方法。这也就是为什么我们声明的Person对象的toString方法的由来。
我们尝试一层一层打印
也就是
__proto__,可以发现最后Object的Prototype的__proto__指向了null,也就是原型链的终点。
也就是codeType
person1(实例对象)
__proto__ → Person.prototype(构造函数原型)
__proto__ → Object.prototype(顶级原型)
__proto__ → null(原型链末端)
三、总结
构造函数、原型对象、实例三者间的关系
- 每个 构造函数(Constructor) 都有一个 prototype 属性,指向 原型对象(Prototype)
- 原型对象(Prototype)有一个 constructor 属性,指向 对应的构造函数
- 每个 实例对象(Instance) 都有一个 proto 属性,指向 构造函数的原型对象(Prototype)
- 实例对象通过 proto 访问原型对象的属性 / 方法,通过原型链实现继承
codeType
1. Person(构造函数)
↓ 拥有 prototype 属性(函数独有)
Person.prototype(原型对象)
2. Person.prototype(原型对象)
↓ 拥有 constructor 属性(原型对象默认自带)
Person(构造函数)(形成构造函数与原型的闭环)
3. person1、person2(实例对象)
↓ 拥有 __proto__ 属性(所有非 null 对象都有)
Person.prototype(原型对象)(实例与原型直接关联)
- 构造函数是用于创建实例的普通函数,需通过 new 调用,首字母大写是约定
- 构造函数的 prototype 是原型对象,用于存放实例共享的属性 / 方法,包含 constructor 指向构造函数
- 实例对象的 proto 指向构造函数的 prototype,这是实例访问原型成员的关键
- 原型链是 proto 串联的对象链,是 JS 继承的核心,遵循「自身→原型→原型的原型→...→Object.prototype→null」的查找规则
- 所有对象的原型链最终都会指向 Object.prototype,null 是原型链的末端