JavaScript 中的原型与原型链
原型与原型链是 JavaScript 面向对象体系的基础,也是高频面试题。理解它们可以帮助你:
- 理解
new、class、extends 等语法糖的本质
- 正确认知“继承”“共享方法”等行为
- 更好地阅读框架/库的底层实现
本文从“代码出发”,逐步讲清楚:
__proto__、prototype、constructor 三者的关系
- 原型链是如何实现“属性查找”的?
- 如何用 ES5/ES6 实现继承?
一、对象的原型:proto 与 [[Prototype]]
在 JavaScript 中,每个对象都有一个内部隐藏属性 [[Prototype]],指向另一个对象(或 null),这个对象就是它的 原型(prototype)。
大多数环境提供了非标准但实用的访问方式:
1 2
| const obj = {}; console.log(obj.__proto__);
|
ES6 也提供了标准方法:
1 2
| Object.getPrototypeOf(obj); Object.setPrototypeOf(obj, someProto);
|
二、函数的 prototype 属性
每一个函数(除箭头函数等少数例外)在创建时,都会自动获得一个 prototype 属性:
1 2 3 4
| function Person() {}
console.log(typeof Person.prototype); console.log(Person.prototype.constructor === Person);
|
Person.prototype 的作用:
- 当你使用
new Person() 创建实例时,新对象的 [[Prototype]] 会指向 Person.prototype。
示意:
1 2 3 4 5
| function Person() {} const p = new Person();
console.log(Object.getPrototypeOf(p) === Person.prototype); console.log(p.__proto__ === Person.prototype);
|
三、constructor、proto、prototype 之间的关系
用一张关系图概括(文字版):
1 2 3 4 5 6 7
| p.__proto__ === Person.prototype Person.prototype.constructor === Person
// 函数本身也是对象 Person.__proto__ === Function.prototype Function.prototype.__proto__ === Object.prototype Object.prototype.__proto__ === null
|
可以记住几个关键点:
- 实例对象的
__proto__ 指向构造函数的 prototype
- 构造函数原型对象(
prototype)上有 constructor 指回构造函数
- 函数本身也是对象,其原型链最终也指向
Object.prototype
四、原型链:属性查找的路径
当访问对象属性时,JavaScript 引擎会沿着原型链逐级查找:
1 2 3
| const obj = { a: 1 };
console.log(obj.toString);
|
查找顺序:
- 先在
obj 自身查找是否有 toString 属性
- 若没有,沿着
obj.__proto__(即 Object.prototype)查找
- 再没有,则沿着更上一层(
Object.prototype.__proto__ 为 null)终止
原型链 就是由 [[Prototype]](或 __proto__)串联起来的一条链:
obj → Object.prototype → null
1. 自定义构造函数的原型链
1 2 3 4 5
| function Animal() {} const cat = new Animal();
|
属性查找会沿着这条链向上走,直到找到为止或到达 null。
五、在原型上共享方法
给构造函数的 prototype 添加方法,可以在所有实例之间共享,而不是每个实例都创建一份:
1 2 3 4 5 6 7 8 9 10 11 12
| function Person(name) { this.name = name; }
Person.prototype.sayHi = function () { console.log("Hi, I'm " + this.name); };
const p1 = new Person("A"); const p2 = new Person("B");
console.log(p1.sayHi === p2.sayHi);
|
这比在构造函数内部定义方法更节省内存:
1 2 3 4
| function Person(name) { this.name = name; this.sayHi = function () {}; }
|
六、ES5 中的继承(原型链继承)
1. 最基本的原型链继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function Animal(name) { this.name = name; } Animal.prototype.sayName = function () { console.log(this.name); };
function Dog(name, age) { Animal.call(this, name); this.age = age; }
Dog.prototype = Object.create(Animal.prototype); Dog.prototype.constructor = Dog;
Dog.prototype.bark = function () { console.log("woof"); };
const d = new Dog("Lucky", 2); d.sayName(); d.bark();
|
这里用到了一个关键方法:
1
| Dog.prototype = Object.create(Animal.prototype);
|
含义:
- 创建一个新的对象,其
__proto__ 为 Animal.prototype
- 然后把这个新对象赋值给
Dog.prototype
这样,Dog 的实例在原型链上会先找到自己的 bark,再往上找到 Animal.prototype 上的 sayName。
七、ES6 class 与原型的关系
ES6 的 class 其实是原型的语法糖:
1 2 3 4 5 6 7 8 9 10 11 12
| class Person { constructor(name) { this.name = name; }
sayHi() { console.log("Hi, I'm " + this.name); } }
const p = new Person("Sunjc"); p.sayHi();
|
背后等价于:
1 2 3 4 5 6 7
| function Person(name) { this.name = name; }
Person.prototype.sayHi = function () { console.log("Hi, I'm " + this.name); };
|
继承时:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class Animal { constructor(name) { this.name = name; } sayName() { console.log(this.name); } }
class Dog extends Animal { constructor(name, age) { super(name); this.age = age; }
bark() { console.log("woof"); } }
|
本质上还是在设置原型链:
1 2 3 4
| Dog.prototype = Object.create(Animal.prototype); Dog.prototype.constructor = Dog; Object.setPrototypeOf(Dog, Animal);
|
八、常见面试题与易错点
1. instanceof 的判断原理
1
| obj instanceof Constructor;
|
判断逻辑:
- 看
Constructor.prototype 是否出现在 obj 的原型链上
1 2 3 4 5 6
| function Foo() {} const f = new Foo();
console.log(f instanceof Foo); console.log(f instanceof Object); console.log(Foo.prototype.isPrototypeOf(f));
|
2. Object.create 与 new 的区别
1
| const obj = Object.create(proto);
|
- 只会创建一个空对象,其
__proto__ 指向 proto
- 不会执行构造函数,不会初始化实例属性
而 new Constructor():
九、总结
JavaScript 的原型与原型链可以概括为几条核心规则:
- 每个对象内部都有一个
[[Prototype]] 指向其原型对象
- 函数都有一个
prototype 属性,用于创建实例时设置其 [[Prototype]]
- 属性访问时,会沿着原型链自下而上查找,直到
null
- 通过在原型上挂方法,可以在实例之间共享逻辑
class/extends 是基于原型链的语法糖,本质仍是对 prototype 与 __proto__ 的配置
掌握这些概念后,再结合 new 的实现、instanceof 的判断、ES5/ES6 继承写法,就能从容应对与原型相关的所有面试题与实际编码场景。