原型与原型链
一、用法比较
| 名词 | 归属 | 作用 | 代码示例 |
|---|---|---|---|
| 构造函数 | 函数 | 创建对象的模板,通过 new 调用 |
function Person(name) { this.name = name } |
| prototype(显式原型) | 仅函数拥有 | 存放所有实例共享的属性/方法 | Person.prototype.sayHi = function() {} |
| proto(隐式原型) | 所有对象拥有 | 指向创建该对象的构造函数的 prototype,构成原型链 | const p = new Person(); p.__proto__ === Person.prototype |
实例的 __proto__ 严格等于其构造函数的 prototype
JavaScript
// 代码验证核心规则
function Person(name) {
this.name = name; // 实例独有属性
}
const p = new Person("Jack");
console.log(p.__proto__ === Person.prototype); // true(核心等式)
console.log(Object.getPrototypeOf(p) === Person.prototype); // ES6推荐写法,结果一致
二、逻辑图

三、为什么要原型?
1. 共享方法,节省内存
反例 不用原型,内存浪费
JavaScript
function Person(name) {
this.name = name;
// 每个实例都会创建独立的sayHi函数,实例越多内存占用越大
this.sayHi = function() {
console.log(`Hi, ${this.name}`);
};
}
const p1 = new Person("Jack");
const p2 = new Person("Lucy");
console.log(p1.sayHi === p2.sayHi); // false(两个不同的函数实例)
比较(用原型,方法共享)
JavaScript
function Person(name) {
this.name = name;
}
// 所有实例共享同一个sayHi方法
Person.prototype.sayHi = function() {
console.log(`Hi, ${this.name}`);
};
const p1 = new Person("Jack");
const p2 = new Person("Lucy");
console.log(p1.sayHi === p2.sayHi); // true(共享同一个函数)
p1.sayHi(); // Hi, Jack
p2.sayHi(); // Hi, Lucy
2. 目的:实现继承
ES6的class只是语法糖,底层依然依赖原型链实现继承。
四、原型链
定义
当访问对象的属性/方法时,JS引擎会按「自身 → 原型 → 原型的原型 → ... → Object.prototype → null」的顺序查找,这条查找链路就是原型链。
JavaScript
function Person(name) {
this.name = name; // 实例自身属性
}
// 原型对象上的方法
Person.prototype.sayHi = function() {
console.log(`Hi, ${this.name}`);
};
const p = new Person("Jack");
// 1. 查找自身属性:name → 存在,直接返回
console.log(p.name); // Jack
// 2. 查找方法:sayHi → 自身没有,去__proto__(Person.prototype)找 → 存在
p.sayHi(); // Hi, Jack
// 3. 查找toString方法:自身和Person.prototype都没有 → 去Object.prototype找 → 存在
console.log(p.toString()); // [object Object]
// 4. 查找不存在的属性:abc → 查找到null仍未找到 → 返回undefined
console.log(p.abc); // undefined
五、constructor
constructor是原型对象(prototype)上的默认属性,默认指向创建该原型对象的构造函数。
JavaScript
function Person() {}
console.log(Person.prototype.constructor === Person); // true
const p = new Person();
// p自身无constructor,通过原型链查找Person.prototype.constructor
console.log(p.constructor === Person); // true
关键场景:继承时修正constructor指向
JavaScript
// 父构造函数
function Parent() {
this.type = "parent";
}
// 子构造函数
function Child() {}
// 实现原型继承:子类原型指向父类实例
Child.prototype = new Parent();
// 此时constructor指向被篡改,需要修正
console.log(Child.prototype.constructor === Parent); // true(错误)
Child.prototype.constructor = Child; // 手动修正
console.log(Child.prototype.constructor === Child); // true(正确)
const child = new Child();
console.log(child.constructor === Child); // true
六、应用场景
1.实现方法共享
JavaScript
// 构造函数
function User(username, age) {
this.username = username;
this.age = age;
}
// 原型上定义共享方法
User.prototype.login = function() {
console.log(`${this.username} 登录成功`);
};
User.prototype.getInfo = function() {
return { username: this.username, age: this.age };
};
// 实例化
const user1 = new User("zhangsan", 20);
const user2 = new User("lisi", 22);
user1.login(); // zhangsan 登录成功
user2.login(); // lisi 登录成功
console.log(user1.getInfo()); // { username: 'zhangsan', age: 20 }
console.log(user1.login === user2.login); // true(方法共享)
2. 实现原型继承
JavaScript
// 父类:动物
function Animal(name) {
this.name = name;
this.eat = function() { // 实例独有方法(非共享)
console.log(`${this.name} 吃东西`);
};
}
// 父类原型方法(共享)
Animal.prototype.run = function() {
console.log(`${this.name} 跑起来`);
};
// 子类:狗
function Dog(name, breed) {
Animal.call(this, name); // 继承父类实例属性
this.breed = breed; // 子类独有属性
}
// 核心:继承父类原型方法
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // 修正constructor
// 子类原型方法
Dog.prototype.bark = function() {
console.log(`${this.name} 汪汪叫`);
};
// 测试
const erha = new Dog("二哈", "哈士奇");
erha.eat(); // 二哈 吃东西(继承父类实例方法)
erha.run(); // 二哈 跑起来(继承父类原型方法)
erha.bark(); // 二哈 汪汪叫(子类原型方法)
console.log(erha.breed); // 哈士奇(子类独有属性)
3.扩展内置对象方法
JavaScript
// 给Array扩展去重方法
if (!Array.prototype.unique) { // 避免重复定义
Array.prototype.unique = function() {
return [...new Set(this)];
};
}
// 给String扩展去除空格方法
if (!String.prototype.trimAll) {
String.prototype.trimAll = function() {
return this.replace(/\s+/g, "");
};
}
// 测试
const arr = [1, 2, 2, 3, 3, 3];
console.log(arr.unique()); // [1, 2, 3]
const str = " he llo world ";
console.log(str.trimAll()); // helloworld
4. 手写new关键字
JavaScript
/**
* 手写new关键字
* @param {Function} fn 构造函数
* @param {...any} args 构造函数参数
* @returns {Object} 实例对象
*/
function myNew(fn, ...args) {
// 1. 创建空对象
const obj = {};
// 2. 绑定原型:obj.__proto__ 指向构造函数的prototype
obj.__proto__ = fn.prototype;
// 3. 执行构造函数,绑定this到obj
const result = fn.apply(obj, args);
// 4. 如果构造函数返回对象,返回该对象;否则返回obj
return result instanceof Object ? result : obj;
}
// 测试
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function() {
console.log(`我是${this.name},${this.age}岁`);
};
const p = myNew(Person, "Tom", 18);
console.log(p.name); // Tom
console.log(p.age); // 18
p.say(); // 我是Tom,18岁
console.log(p.__proto__ === Person.prototype); // true
5. 判断数据类型
JavaScript
/**
* 精准判断数据类型
* @param {any} data 要判断的数据
* @returns {string} 类型字符串(如"Array"、"Object"、"Number")
*/
function getType(data) {
return Object.prototype.toString.call(data).slice(8, -1);
}
// 测试
console.log(getType([])); // Array
console.log(getType({})); // Object
console.log(getType(123)); // Number
console.log(getType("abc")); // String
console.log(getType(null)); // Null
console.log(getType(undefined)); // Undefined
console.log(getType(() => {})); // Function
七、经典面试题合集(附答案+解析)
1. prototype 和 proto 的区别?
-
prototype:仅函数拥有,是「显式原型」,用于存放所有实例共享的属性/方法;
-
proto:所有对象(包括函数)都拥有,是「隐式原型」,指向创建该对象的构造函数的prototype,是原型链查找的核心链路。
2. 原型链的终点是什么?为什么?
-
终点是
null; -
原因:
Object.prototype.__proto__ === null,Object.prototype是所有对象的顶级原型,其隐式原型指向null,代表原型链查找终止。
3. 如何判断一个属性是对象自身的,还是原型上的?
使用 hasOwnProperty() 方法(来自Object.prototype):
JavaScript
function Person() {
this.name = "Jack";
}
Person.prototype.age = 18;
const p = new Person();
console.log(p.hasOwnProperty("name")); // true(自身属性)
console.log(p.hasOwnProperty("age")); // false(原型属性)
4. ES6的class和原型的关系?
ES6的class是原型链的「语法糖」,底层依然基于原型实现:
JavaScript
// ES6 class写法
class Person {
constructor(name) {
this.name = name;
}
sayHi() {
console.log(`Hi, ${this.name}`);
}
}
// 等价于ES5原型写法
function Person(name) {
this.name = name;
}
Person.prototype.sayHi = function() {
console.log(`Hi, ${this.name}`);
};
5. 执行 Object.prototype.toString.call([]) 为什么返回 "[object Array]"?
-
toString是Object.prototype上的方法,默认返回对象的类型信息; -
数组自身重写了toString方法(返回数组元素拼接字符串),通过
call改变this指向,调用的是Object.prototype上的原始toString方法,因此能精准返回类型。
八、总结
-
核心等式 :实例.proto === 构造函数.prototype(原型的核心关联);
-
原型链规则:属性/方法查找遵循「自身 → 原型 → 原型的原型 → ... → Object.prototype → null」;
-
核心价值:原型实现属性/方法共享(节省内存),原型链实现JS继承(面向对象的底层);
-
关键方法 :
hasOwnProperty()(判断自身属性)、Object.getPrototypeOf()(ES6获取隐式原型)、Object.create()(实现原型继承)。