原型的概念还是非常复杂且容易令人产生错误的,本文第一版就有很多错误概念,这里再修改后,就相对少多了 (应该吧,欢迎指出错误)
1. 原型概念
JS 是一门基于原型的语言,其他语言通过类描述和实例化对象,JS 通过构造函数描述和实例化对象,通过原型实现对象继承。
JS 中几乎每个对象都有隐式原型 __proto__
,相当于他的父对象,但是基本没有显式原型 prototype
(除了函数),这里的对象可以是普通的对象、也可以是函数等,他们都会继承有其他的东西
每个构造函数上都有一个显式原型,用于描述创建的对象的属性和行为,对象的隐式原型等于它构造函数的显式原型
当对象上找不到某个属性时,他就会往原型上寻找,原型上没有就会向原型的原型上寻找,这也就是原型链的基本概念
2. 基本原型三角
- 创建一个函数
A
,在A
中使用this
描述要创建的对象的属性和方法,prototype
描述在创建的对象上继承的属性和方法,修改prototype
会影响所有实例化的对象 - 实例化对象可以通过
contructor
获取到构造函数的引用,通过__proto__
获取到原型 - 函数
A
的原型的构造函数实际上是函数A
本身
function A() {}
const f1 = new A();
// __proto__ 的引用和 prototype 的引用是相等的,但是如果修改了prototype,__proto__ 的引用不会改变
f1.__proto__ === A.prototype; // true
// f本身是没有constructor属性的,它是通过原型链找到的
f1.__proto__.constructor === A; // true
// 如果修改prototype是一个有原型的对象,新的实例化对象的构造函数不指向A
function B() {}
A.prototype = {
constructor: B,
};
const f2 = new A();
f2.__proto__.constructor === A; // false
f2.__proto__.constructor === B; // true
// 因为f1.__proto__ 还是原来那个对象,所以f1的构造函数还是A
f1.__proto__.constructor === A; // true
3. 对象原型三角
- 任何一个没有其他继承关系的对象的隐式原型,都是
[Object: null prototype]{}
,而他的隐式原型最终会指向null
,这也就得到了原型链的终点null
Object
构造函数的显式原型是[Object: null prototype]{}
const obj1 = new Object()
obj1.__proto__ // [Object: null prototype] {}
obj1.__proto__ === Object.prototype // true
obj1.__proto__.__proto__ // null
obj1.__proto__.constructor === Object // true
注:在浏览器控制台可以打印对象结构,以下是各个对象的结构
Object
- 实例化对象
obj1
obj1
的隐式原型/Object
的显式原型
4. 函数原型三角
函数的构造函数和实例化函数之间的关系也比较特殊
Function
的显式原型和隐式原型是同一个函数,这个函数的constructor
指向Function
- 实例化函数
f
默认自带一个显式原型,这和普通的实例化对象不一样,同时这个显式原型自带一个constructor
指向f
f
的隐式原型等于Function
的原型
注:在浏览器控制台可以打印对象结构,以下是各个对象的结构
Function
- 实例化函数
f
Function
的原型函数f
的显式原型prototype
5. 函数和对象的原型关系
除了函数本身的原型关系,它的原型和对象也有一定的原型关系
Function.prototype // f () { [native code] }
Function.__proto__ === Function.prototype // true
Function.__proto__.constructor === Function // true
Function.__proto__.__proto__ // [Object: null prototype] {}
Object.__proto__ // f () { [native code] }
Object.__proto__ === Function.prototype // true
Object.__proto__.constructor === Function // true
6. 原型的全貌
根据上面的分析,就可以得出原型的全貌图了,直接看的话还是太抽象了,可以拆开分析,使用代码验证。