先来张图
如果这张图能看懂的话,就不需要浪费时间往下看了,直接毕业。
有点蒙的话没关系,把下面内容搞懂之后,再回头看看。
先了解两个概念
什么是构造函数?
答:如果一个函数通过new 关键字调用的话,这个函数就是构造函数。并且我们规定首字母得大写(当然首字母不大写也没问题但不规范)。通过new关键字调用函数会返回一个对象。记住这一点,很重要。
什么是对象?
在我刚学js的时候,我常听到的一句话就是在js中万物皆对象,后来看到一本书《你不知道的js》,才明白这种说法其实是不对的。
对象是JavaScript的核心概念之一,是一种最常用的数据类型。数据类型可以分为两大类:简单基本类型和复杂基本类型
简单基本类型(string、number、boolean、null、undefined)本身并不是对象。null有时会被当成一种对象类型,但是这其实是语言本身的一个bug,即对null执行typeof null时会返回字符串”object“。
原理是这样的,不同的对象在底层都表示为二进制,在JavaScript中二进制前三位都为0的话会被判断为object类型,null的二进制表示是全0,自然前三位也是0,所以执行typeof时会返回“object”。
js中有许多特殊的对象子类型,我们称之为复杂基本类型。比如函数就是对象的一个子类型(从技术角度来说就是”可调用对象“),包括数组也是对象的一种类型。
关于原型中的几个关键属性,如下:
一、prototype
js中所有 函数 都有一个属性 prototype,指向一个对象,这个对象就是该函数的原型对象
比如我写了一个函数:function test(){}
想查看或者操作这个函数的原型,则通过test.prototype得到该函数的原型
当然,js内置的函数对象的原型也同理,例如,Object.prototype,Function.prototype,Array.prototype,Number.prototype等等。。分别是这些函数的原型对象
二、__proto__
js中所有 对象 (除了null)(包括原型对象,也就是某函数的prototype) 都具有一个__proto__属性,该属性指向了该对象构造函数的原型。
注意!不是该对象的原型,普通对象自身没有原型,是构造函数的原型,哪个函数构造的该对象,则指向哪个函数的原型
比如我定义了一个对象:let obj = {} ,或者更直观定义 let obj = new Object();
想查看或者操作这个对象的原型,则可以通过obj.__proto__得到该对象构造函数的原型。
因此,obj.__proto__ 就是 Object.prototype。他们指向的是同一个原型对象
同理,随便定义一个数组对象,let arr = [], arr.__proto__ 就是Array.prototype
再同理,随便定义一个自定义函数对象,function test(){}, test.__proto__ 就是Function.prototype(这里应该能够发现,函数对象自己也有原型,这是与其他对象不同的地方,别忘了,函数是可以作为构造函数,构建对象的哟)
知道了这些有什么用呢,可以看下面这波操作
假如我们在Object.prototype上定义了一个属性,Object.prototype.makabaka = "玛卡巴卡"
则我们定义的所有对象,let obj1 = {} ; let obj2 = {} ,都可以通过调用__proto__.makabaka,拿到”玛卡巴卡“
例如:
obj1.__proto__.makabaka === "玛卡巴卡"
obj2.__proto__.makabaka === "玛卡巴卡"
你注意啦!这里有个神奇操作,__proto__是可以不写的。
obj1.makabaka === "玛卡巴卡"
obj2.makabaka === "玛卡巴卡"
如果不写__proto__的话js会先在obj1自身上找一下有没有makabaka属性,如果js发现obj1上没有makabaka属性,就会通过obj1.__proto__.makabaka,去obj1的原型上找这个属性,这就是传说中的原型链。
三、constructor
js中所有 原型对象 (prototype)都有一个constructor属性,该属性指向了原型对象的构造函数。
既然可以通过函数得到原型,那么也可以通过原型得到构造函数
还是写一个函数:function test(){}
他的原型对象为 test.prototype,所以test.prototype.constructor 的结果为 test(){}
我们使用更多的可能是一个普通对象调用constructor,来通过这个属性判断,是否是某个函数构建的。
例如:let a = []; a.constucotr 的结果为Array()
注意了,数组对象a本身是没有constucotr属性的,这个属性是在原型上的,上面讲过了,如果对象本身没有该属性,会获取a.__proto__.constucotr,得到该原型对象的constucotr属性,进一步得到构造函数。
不过由于prototype是可以随时修改的,不太安全,也不建议用这个来判断,建议使用instanceof来判断某个对象跟构造函数的关系。这里只需要了解他的作用就好。
我们再来归类总结一下,通过几个实例再来理解一下。
一、所有对象(包括Function),他们的__proto__属性指向的是Function.prototype(Function函数的原型)。(函数也是对象,因此所有对象都有__proto__属性)
// 下面判断结果都为true,(Object、Number、String等内置对象,都是通过Function构造生成的)
Object.__proto__ === Function.prototype
Number.__proto__ === Function.prototype
String.__proto__ === Function.prototype
Array.__proto__ === Function.prototype
Function.__proto__ === Function.prototype
function test() {}
test.__proto__ === Function.prototype
....
二、所有对象原型(prototype)的__proto__都是指向Object.prototype的
// 下面判断结果都为true
Function.prototype.__proto__ === Object.prototype
Number.prototype.__proto__ === Object.prototype
String.prototype.__proto__ === Object.prototype
Array.prototype.__proto__ === Object.prototype
function test() {}
test.prototype.__proto__ === Object.prototype
...
三、自定义对象实例的__proto__指向构造函数的原型(谁创造的我,我就指向谁的原型)
// 下面判断结果都为true
let obj = {}; // let obj = new Object()
obj.__proto__ === Object.prototype
function Test(){}
let obj2 = new Test()
obj2.__proto__ === Test.prototype
let arr = []; //let arr = new Array()
arr.__proto__ === Array.prototype
...
四、Object.prototype.__proto__ 指向null
Object.prototype.__proto__ === null
将原型与原型链方面的知识凝结成一张图:
最后,在上面所有例子的基础上,最后再来看一下完整原型链查找过程
Object.prototype.makabaka = "玛卡巴卡"
function Test(){}
let obj = new Test()
obj.makabaka === "玛卡巴卡"
// 查找路径如下
obj.makabaka // 在obj上没找到玛卡巴卡
// obj.__proto__ === Test.prototype
obj.__proto__.makabaka // 在Test.prototype 上没找到玛卡巴卡
// obj.__proto__.__proto__ === Test.prototype.__proto__ === Object.prototype
obj.__proto__.__proto__.makabaka // 终于在Object.prototype上找到了玛卡巴卡
相信看到这里再看文章开头的图片,就会一目了然了
当然,关于原型链这部分知识,远不止本文所讲这些,像js中类的概念就是在原型的基础上建立的。