JS之原型与原型链
1.为什么要有原型和原型链
JavaScript中其实并没有类的概念。从es6通过class
关键字才引入了Class
这个概念,但是ES6的class
可以看作是一个语法糖,它的绝大部分功能,ES5都可以做到。。
es6之前,实现生成实例对象的传统方法是通过构造函数。将实例对象与构造函数连接起来的就是原型
与原型链
。
2.什么是原型?
JavaScript中有两个原型:
__proto__
:隐式原型prototype
:显式原型
但是,这两种原型并不存在于JavaScript的所有对象中。那么在哪里可以体现出这两个原型呢?先看代码:
var a = []
var o = {}
function fn(){}
console.log(a.__proto__); // 非undefined
console.log(a.prototype); // undefined
console.log(o.__proto__); // 非undefined
console.log(o.prototype); // undefined
console.log(fn.__proto__); // 非undefined
console.log(fn.prototype); // {constructor: f}
结果如下:
可以看到:
-
数组、对象、函数三个引用类型都有
__proto__
属性(隐式原型) -
只有函数除了
__proto__
之外还有一个prototype
属性(显式原型)
所有的函数默认都会拥有一个名为prototype
的公有且不可枚举(enumerable)的属性,它会指向另一个空的对象。
下面用构造函数来说明:
可以看到,a是通过构造函数创建的一个新对象,通过a.__proto__
可以看到foo.prototype
的内容。其实a.__proto__
就是foo.prototype
的引用,它俩指向的都是同一个地址(a.__proto__ === foo.prototype // true
)。
小结:
-
实例对象a只有隐式原型(
__proto__
);构造函数既有隐式原型(__proto__
)也有显式原型(prototype)。 -
__proto__
和prototype
都是对象,是对象说明它们也都有.__proto__
属性:a.__proto__.__proto__ foo.prototype.__proto__
-
实例对象的隐式原型指向了构造函数的显式原型:
a.__proto__ === foo.prototype
-
访问实例对象的某个属性,如果实例对象上没有,就通过
实例对象.__proto__
找到构造函数的prototype继续找。function foo() { } foo.prototype.name = "张三" var a = new foo() a.name // 张三
3. 原型链
什么是原型弄懂了,原型链是什么大致也就明了了。先看如下代码的输出:
function foo() { }
foo.prototype.name = "张三"
var a = new foo()
console.log('1------', a.__proto__);
console.log('2------', a.__proto__.__proto__);
console.log('3------', a.__proto__.__proto__.__proto__);
console.log(a.name); // 张三
console.log(a.toString()); // [object Object]
结果如下:
可以看到:
-
a.__proto__
:实例对象a的__proto__
,指向的是构造函数foo的prototype
-
a.__proto__.__proto__
:实例对象a的__proto__
的__proto__
指向的是Object()
构造函数对象。 -
a.__proto__.__proto__.__proto__
:Object
对象的__proto__
最终指向了null。
这样一级一级的__proto__
相连接起来的链式结构就是**原型链。
而在最后两行的console
中:
-
a.name
输出的是"张三",但是实例对象a并没有name
这个属性,它是js引擎通过原型链查找到a.__proto__
(即foo.prototype
)找到的。 -
a.toString()
这个方法在foo构造函数
以及a实例对象
中均没有,它存在于原型链中的a.__proto__.__proto__
找到了Object()
构造函数对象的prototype
(显式原型)上。
因此,在查找一个对象的属性时,JS引擎就是在这样的原型链上一级一级的向上查找的,如果找到顶层的Object对象的__proto__
还是没有找到某个属性,就会抛出错误。
4.关于constructor
可以看到,不管是实例对象a还是构造函数foo,又或者是Object()
构造函数。它们的__proto__
(隐式原型)上都有一个constructor
属性。
这个constructor
属性是所有函数的prototype
属性自带的一个属性,它不可枚举(enumerable = false)且通过new foo()
创建的对象也有一个constructor
属性,指向了这个对象的构造函数。所以,实例对象、构造函数的原型、构造函数三者的关系是这样的:
a.constructor === foo.prototype === foo
其实,构造函数的原型上的constructor就是当前这个构造函数的引用:
function foo() { }
console.log(foo.prototype.constructor === foo); // true