抛砖引玉
以下是JavaScript中的一个简单的new操作
// ES5中通过构造函数
function Foo() {
// ...
}
let f1 = new Foo();
// ES6中通过class
class Foo2() {
constructor() {
// ...
}
}
let f2 = new Foo2();
如图所示,尽管只是一个非常简单且常规的操作,但是他在JS中的执行过程是十分的复杂的,而这也就涉及这五个概念:prototype、[[Prototype]]、\_\_proto\_\_、constructor、原型链,以下将围绕这张图对其进行逐一讲解,建议先看最后面的总结,再来看以下内容,最后再理解一遍总结内容加深理解
图片中函数后面加括号仅用于更清楚的表示其为一个函数,不代表函数执行后的返回值,下文也是如此
prototype
原型,又叫原型对象,类型是一个对象(Object),仅函数(Function)有该属性(类Class的本质也是函数)
用于构造函数或类为其实例对象(通过关键字new
创建)提供公共的属性和方法
[[Prototype]]
隐式原型,仅对象才拥有,它指向自己的prototype原型对象(或者说父对象),可以通过Object.getPrototypeOf()
和Object.setPrototypeOf()
方法进行访问和设置
\_\_proto\_\_
[[prototype]]是规范和一些书籍对隐式原型的表示方法,\_\_proto\_\_ 是浏览器厂商对隐式原型的实现,其实就是一个东西;过去浏览器显示为__proto__,现在改为另外一种显示呢,等同于[[Prototype]],是许多浏览器和JavaScritp引擎的非ECMAScript标准实现
__proto是对象所独有的,通过直接访问该属性即可更改
console.log(Object.getPrototypeOf(f1) === f1.__proto__) // true
但是这是非ECMAScript标准,会有安全问题,因此更推荐使用Object.getPrototypeOf()
和Object.setPrototypeOf()
方法,这里将使用__proto__
进行说明
它的作用也是一致的,指向自己的构造函数的prototype原型对象(或者说父对象),因此下列输出成立
console.log(f1.__proto__ === Foo.prototype) // true
有了以上知识我们可以正式开始讲解该图片了,先不管别的,来看最左边的一列,我们说过,prototype它是一个对象,因此它也有__proto__属性,而它的属性指向哪了?显然,只要我们没有特意设置它的指向的话,那便是Object.prototype,再继续向下找__proto__那便是null,这一个接连一个的查找形成了一个链条,这便是大名鼎鼎的原型链,而null便是原型链的终点。
这里扩展一下,我们说了,prototype是对象类型的,而终点null是一种特殊的存在,表示不可在继续查找下去。null 虽然用typeof打印出来的类型为 "object", 但它的真实类型应该是Null ,表示一个空对象指针。它不是一个对象类型,不能调用对象的方法。
console.log(typeof null) // object
这里我们换一个例子来进一步理解原型链
let arr = new Array();
console.log(arr.__proto__ === Array.prototype);
console.log(Array.prototype.__proto__ === Object.prototype);
使用Array()
构造函数来创造了一个数组实例arr
,数组arr的隐式原型指向数组原型对象Array.prototype,因此第一个输出成立,然后Array.prototype又指向对象的prototype,再次成立,最后指向null
关于函数
如果只是对象的话,那么这个图便不会如此复杂,但因为函数的存在大大增加了这个图的复杂度,函数的本质也是一个对象,因此函数既有__proto__和prototype,这也是许多人迟迟搞不懂原型链的原因,不用想那么多,一个函数的__proto__与prototype是完全没有任何关系的
现在先不管所有的constructor,Foo()
函数的__proto__显而易见的指向了一个叫做Function.prototype的东西,没错他便是构造函数Function()的原型对象,而一切的函数都由Function()创造,如图所示,它的__proto__又指向了Object()
函数的原型对象Object.prototype,再而指向null,相反的,Object()
是一个构造函数,因此__proto__又指向Function.prototype
关于函数还有一个需要注意的一点,那就是图中Function的prototype和__proto__是相等的。可以理解为,所有函数的__proto__都指向Function()
构造函数的原型对象,而Function()本身也是函数又是对象,所以自己的__proto__属性指向自己的原型对象Function.prototype
console.log(Function.__proto__ === Function.prototype); // true
constructor
与__proto__类似,对象独有的属性,用于原型对象(prototype)指回其构造函数本身。如图所示,一个的函数prototype和它的constructor的关系是:函数通过prototype
属性访问其原型对象,而原型对象上有一个constructor
属性指回其构造函数本身
而实例对象(如图中的f1)它的constructor也指向Foo()
,但只是继承而来的并不真正拥有(因为f1它是Foo()的实例)
需要注意一点,所有的函数的constructor最终都继承来自Function()
构造函数,而Function()
的constructor又继承于自身
总结
- prototype仅函数(
class类
也是函数)拥有,用于给自己实例化对象提供公共公告属性或对象 - \_\_proto\_\_和constructor仅对象拥有,因为函数本质也是对象所以函数同时拥有以上三者。\_\_proto\_\_指向自己的原型对象(即prototype),因此
f1.__proto__ === Foo.prototype
- 查找一个实例对象的属性或方法先从自身开始查找,若没有则查找自己构造函数的
prototype
;若没有,继续查找函数的prototype
属性里的__proto__属性的又一个原型对象,直至查找到Object.prototype
,再往下的__proto__便为null
,这样的链式查找方式便是原型链,null
是原型链的终点 constructor
用于prototype里指回自己的构造函数,实例对象继承于自己的构造函数,即Foo === Foo.prototype.constuctor
- 所有对象的prototype最终指向
Object.prototype
,所有对象的constuctor最终指向Function()
构造函数,Function()
的constructor继承于自己
鸣谢
本文参考了以下链接中的内容并综合自己的个人理解,如若有误欢迎指出,排名分先后
- https://chen-cong.blog.csdn.net/article/details/81211729
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain
- https://www.bilibili.com/video/BV1PG411774p
- https://www.bilibili.com/video/BV1Wa4y1N7YS
本文作者:Arimura Sena
本文链接:https://hotaru.icu/archives/255/
版权声明:转载时须注明出处及本声明