1541 字
8 分钟
原型和原型链
什么是原型
:::tip JS原型的概念
javascript
常被描述为一种基于原型的语言(每个对象都拥有一个原型对象)当访问一个对象的属性时,它不仅在该对象上寻找,还会寻找该对象的原型,以及该对象原型的原型,层层向上,直到找到或者到达原型链的末尾
函数可以有属性。每个函数都有一个特殊的属性叫作原型
prototype
:::
function doSomething(){}
console.log( doSomething.prototype );
// 控制台输出
/* {
constructor: ƒ doSomething(),
__proto__: {
constructor: ƒ Object(),
hasOwnProperty: ƒ hasOwnProperty(),
isPrototypeOf: ƒ isPrototypeOf(),
propertyIsEnumerable: ƒ propertyIsEnumerable(),
toLocaleString: ƒ toLocaleString(),
toString: ƒ toString(),
valueOf: ƒ valueOf()
}
}
*/
上面这个对象,就是大家常说的原型对象
可以看到,原型对象有一个自有属性constructor
,这个属性指向该函数,如下图关系展示:
什么是原型链
:::tip 原型链的概念
- 原型对象也有可能拥有原型,并从中继承方法和属性,一层一层,以此类推。这种关系就形成了原型链(这也就是为何一个对象会拥有定义在其他对象中的属性和方法)
- 在对象实例和它的构造器之间建立一个链接(它是
__proto__
属性,是从构造函数的prototype
属性派生的),之后通过上溯原型链,在构造器中找到这些属性和方法
例子:
:::
function Person(name) {
this.name = name
this.age = 20
this.sayName = function() {
console.log(this.name)
}
}
// 创建实例
let person = new Person('Ocean')
根据代码,我们可以得到下图:
TIP
- 构造函数
Person
存在原型对象Person.prototype
- 构造函数生成实例对象
person
,person
的__proto__
指向构造函数Person
原型对象(Person.prototype
)Person.prototype.__proto__
指向内置对象,因为Person.prototype
是个对象,默认是由Object
函数作为类创建的,而Object.prototype
为内置对象Person.__proto__
指向内置匿名函数anonymous
,因为 Person 是个函数对象,默认由 Function 作为类创建Function.prototype
和Function.__proto__
同时指向内置匿名函数anonymous
,这样原型链的终点就是null
小结(结论)
1. __proto__
作为不同对象之间的桥梁,用来指向创建它的构造函数的原型对象的
2. 每个对象的__proto__
都是指向它的构造函数的原型对象prototype
的
person1.__proto__ === Person.prototype
3.构造函数是一个函数对象,是通过 Function
构造器产生的
Person.__proto__ === Function.prototype
4.原型对象本身是一个普通对象,而普通对象的构造函数都是Object
Person.prototype.__proto__ === Object.prototype
5.刚刚上面说了,所有的构造器都是函数对象,函数对象都是 Function
构造产生的
Object.__proto__ === Function.prototype
6.Object
的原型对象也有__proto__
属性指向null
,null
是原型链的顶端
Object.prototype.__proto__ === null
TIP
一切对象都是继承自
Object
对象,Object
对象直接继承根源对象null
:
- 一切对象的原型链最终都是
.... → Object.prototype → null
。例如定义一个num变量var num = 1
,则num的原型链为x → Number.prototype → Object.prototype → null
; 定义一个函数对象fnfunction fn() {}
,则fn的原型链为fn → Function.prototype → Object.prototype → null
;等等…- 一切对象都包含有Object的原型方法,Object的原型方法包括了toString、valueOf、hasOwnProperty等等,在js中不管是普通对象,还是函数对象都拥有这些方法
一切的函数对象(包括
Object
对象),都是继承自Function
对象
- Object继承自Function,Object的原型链为
Object → Function.prototype → Object.prototype → null
,原型链又绕回来了,并且跟第一点没有冲突。可以说Object和Function是互相继承的关系
Object
对象直接继承自Function
对象
Function
对象的__proto__
会指向自己的原型对象,最终还是继承自Object
对象
提出疑问
1. 一切对象继承自Object,Object又继承自Function,那一切对象是不是都有Function的原型方法
TIP答: 不对。普通对象没有Function的原型方法。从我们所写原型链中可以看出,Object是继承自Function,而Object也有Function的原型方法(比如bind),但Object继承得到的方法储存于
__proto__
属性中,普通对象从Object继承到的原型方法却在于prototype
属性中,因而不对。总结一句话: Object对象直接继承自Function对象,一切对象(包括Function对象)直接继承或最终继承自Object对象。
2. Object.__proto__.__proto__.__proto__
输出什么?分析一下
TIP这个问题可以用上面整理的几条结论分析:
- 首先,因为一切函数对象(包括Object)都继承自Function对象,所以
Object.__proto__ === Function.prototype
成立- 又因为一切对象都继承自Object,所以
Object.__proto__.__proto__ === Object.prototype
成立- 最后,Object对象直接继承根源对象null,所以
Object.__proto__.__proto__.__proto__ === null
几道练习题:notebook:
代码输出题:
1.
// 浏览器环境下
var a = function () {this.b = 3}
var c = new a(); // a作为构造函数创建了一个实例c,并且在c上定义了一个属性b = 3
a.prototype.b = 9 // 在a的原型上添加了一个属性b = 9
var b = 7 // 声明了一个全局变量
a() // 调用a函数,此时a的this指向全局变量window,所以覆盖了上一行b的值
console.log(b) // 3 (
console.log(c.b) // 3 (因为实例c上本身就有b属性,所以不会再去原型链上找,即使c.__proto__.b = 9)
2.
function A() {};
function B(a) {
this.a = a;
}
function C(a) {
if (a) {
this.a = a;
}
}
A.prototype.a = 1;
B.prototype.a = 1;
C.prototype.a = 1;
console.log(new A().a); // 1
console.log(new B().a); // undefined
console.log(new C(2).a); // 2
3.
function Foo() {
Foo.a = function() {
console.log(1)
}
this.a = function() {
console.log(2)
}
}
Foo.prototype.a = function() {
console.log(3)
}
Foo.a = function() {
console.log(4)
}
Foo.a(); // 4
let obj = new Foo();
obj.a(); // 2
Foo.a(); // 1
:::tip 解释
- 第一个
Foo.a()
输出为4,此时还没有调用Foo构造函数 obj.a()
输出为2。虽然原型上也有 a 方法。但是实例对象 obj 上也有 a 方法,所以会先在自身找,找不到时才去原型链上找。- 最后一个
Foo.a()
的输出应该为1。调用构造函数Foo之后,Foo内部定义的a就覆盖掉了外层的那个
:::