图解JavaScript:对象、继承与原型链

根据理论绘制示意图
构造函数、原型
每个function都有一个原型,function的
prototype
属性指向了这个function的原型构造函数是function的一个种类,所以
每个object都是由某个构造函数产生的,object的
__proto__
属性指向产生这个object的构造函数的原型
● 每个构造函数都有一个原型,构造函数的prototype
属性指向了这个构造函数的原型
注:原型是从属于function的,应当说“function的原型”,而“object的原型”是“object的构造函数的原型”的简略说法。

function是object的一个种类,所以
产生function的构造函数是Function,即Function是function的构造函数,所以
Function是function的构造函数,所以Function是一个function,所以Function是它本身的构造函数,所以
● 每个function都是由某个构造函数产生的,function的__proto__
属性指向了产生这个function的构造函数的原型
● Function有一个原型,Function的prototype
属性指向了Function的原型
● 每个function都是由Function产生的,function的__proto__
属性指向了Function的原型
● Function的__proto__
属性指向了Function的原型

类、实例
JavaScript没有专门的类定义。
假如有一个名为Person的构造函数,由Person产生了一个名为person1的object,我们可以认为Person是一个类的名字,person1是Person类的一个实例。

图中黄色框表示function,绿色框表示object。
结合前文内容可知,person1是Person类的一个实例,表现为person1的__proto__
属性指向Person的原型。
那么从图中可以发现,Person的__proto__
属性指向Function的原型,所以说Person(一个function)是Function类的一个实例。
这是因为产生function的构造函数是Function,所以所有的function都是Function的实例。
注:prototype
是构造函数的属性,__proto__
是实例的属性。然而,构造函数本身是Function的实例,所以构造函数也有__proto__
属性。
继承
这里直接给出结论:Teacher类继承Person类,表现为Teacher的原型的__proto__
属性指向Person的原型。

图中绿色箭头表示实例化自,蓝色箭头表示继承。
观察图片左下角,我们还可以这样说,Teacher类继承Person类,表现为Teacher的原型是Person类的一个实例。这个说法在后面还会提到。
继续给出如下信息:
Function类继承Object类,即
Object是构造函数,所以Object是一个function,所以Function是Object的构造函数,所以
所有的类都直接或间接地继承Object类,没有指定继承来源的类(如Person类)都默认直接继承Object类,所以
Object类是继承的终点,Object的原型的
__proto__
属性为null
● Function的原型的__proto__
属性指向Object的原型
● Object的__proto__
属性指向了Function的原型
● Person的原型的__proto__
属性指向Object的原型

object、function
上面所说的“名为Person的function”,实际上是一个“名为Person的指向一个function的变量”
同理,“名为person1的object”,实际上是一个“名为person1的指向一个object的变量”
object本身没有名字,而function本身有名字,保存在function的
name
属性中function的
length
属性中保存函数的形参个数所有object都会从它的构造函数的原型上继承一个
constructor
属性,保存对创建该object的构造函数的引用,换句话说,
● 构造函数的原型的 constructor
属性指向该构造函数本身

在浏览器中进行实验
Object
首先尝试在浏览器控制台打印Object
,它应该是一个指向Object类的构造函数的内置变量。

输出的“ƒ”表明这是一个function,“Object()”表明函数名为“Object”且形参列表为空。后面的大括号中本应输出函数体,由于Object()是内置函数,故显示为“[native code]”。
如何把Object类的构造函数作为一个object而不是function打印出来?我们可以手动创建一个object,令它的一个成员变量(即“属性”)指向Object类的构造函数。
执行{Object}
,它是{Object: Object}
的简写,表示使用对象字面量语法(大括号)创建一个object,并把内置变量Object
(指向Object类的构造函数)赋值给这个object的名为Object
的属性。图示如下:

控制台输出如下:

输出的“{Object: ƒ}”表明这是一个object,且它有一个名为Object
的属性,该属性指向一个function(或者说“该属性是一个function”)。
将其展开,可以发现它还有一个名为__proto__
的属性(注意缩进),且__proto__
属性指向一个Object类的实例(即object)。而Object
属性指向一个函数名为“Object”且形参列表为空的function,它就是Object类的构造函数。这个Object
属性可以被展开,即达到了将一个function作为object打印出来的目的。
观察Object类的构造函数,发现它的确有length
、name
、prototype
、__proto__
属性:
length
属性为数值1(注意并不是理论值“0”)name
属性为字符串“Object”prototype
属性指向一个object,它是Object这个构造函数的原型__proto__
属性指向一个function,根据前文所述,它是Function这个构造函数的原型(它并不是一个object,是例外情况),稍后进行验证
我们手动创建的object必为Object类的实例,所以它的__proto__
属性应当与Object类的构造函数的prototype
属性指向同一个object。
我们将它们各自展开,发现它们完全一致:

验证如下:

接着验证Object的原型的 constructor
属性指向Object本身:


注:访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__
属性所指向的那个对象(可以理解为父对象)里找,如果父对象也不存在这个属性,则接着去父对象的__proto__
属性所指向的那个对象(可以理解为爷爷对象)里找,如果还没找到,则继续往上找……直到原型链顶端null
。
最后,Object的原型的__proto__
属性为null
,而不是undefined
(其实 __proto__
是个定义在 Object.prototype
上的访问器属性,稍后会进行说明)。

选取几个方法作为代表,完善示意图:

Function
执行{Function}
,它是{Function: Function}
的简写,表示使用对象字面量语法(大括号)创建一个object,并把内置变量Function
(指向Function类的构造函数)赋值给这个object的名为Function
的属性。图示如下:

控制台输出如下:

输出的“{Function: ƒ}”表明这是一个object,且它有一个名为Function
的属性,该属性指向一个function(或者说“该属性是一个function”)。
将其展开,可以发现Function
属性指向一个函数名为“Function”且形参列表为空的function,它就是Function类的构造函数。
观察Function类的构造函数,发现它的确有length
、name
、prototype
、__proto__
属性:
length
属性为数值1(注意并不是理论值“0”)name
属性为字符串“Function”prototype
属性指向一个function,它是Function这个构造函数的原型__proto__
属性指向一个function,它同样是Function这个构造函数的原型
Function类的构造函数的prototype
、__proto__
属性与Object类的构造函数的__proto__
属性应当指向同一个function,验证如下:



观察Function的原型,由于它是一个function,所以有length
、name
、__proto__
属性,然而特殊之处是它没有prototype
属性:
length
属性为数值0name
属性为空字符串__proto__
属性指向一个object,根据前文所述,它是Object的原型,验证如下:


Function的原型虽然是一个function,但是没有prototype
属性:

最后,验证Function的原型的 constructor
属性指向Function本身:


那么Object.constructor
指向谁?Function.constructor
指向谁?

选取几个方法作为代表,完善示意图(注意Function的原型改用黄色框,表示它是一个function):

使用Javascript中的原型
https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Objects/Object_prototypes#使用javascript中的原型
在控制台执行如下代码,创建doSomething函数:


我们可以添加一些属性到doSomething的原型上面:


然后,我们可以使用new运算符来在现在这个原型的基础上,创建一个doSomething的实例:


就像上面看到的,
doSomeInstancing
的__proto__
属性就是doSomething.prototype
. 但是这又有什么用呢? 好吧,当你访问doSomeInstancing
的一个属性, 浏览器首先查找doSomeInstancing
是否有这个属性. 如果doSomeInstancing
没有这个属性, 然后浏览器就会在doSomeInstancing
的__proto__
中查找这个属性(也就是 doSomething.prototype). 如果 doSomeInstancing 的__proto__
有这个属性, 那么 doSomeInstancing 的__proto__
上的这个属性就会被使用. 否则, 如果 doSomeInstancing 的__proto__
没有这个属性, 浏览器就会去查找 doSomeInstancing 的__proto__
的__proto__
,看它是否有这个属性. 默认情况下, 所有函数的原型属性的__proto__
就是window.Object.prototype
. 所以 doSomeInstancing 的__proto__
的__proto__
(也就是 doSomething.prototype 的__proto__
(也就是Object.prototype
)) 会被查找是否有这个属性. 如果没有在它里面找到这个属性, 然后就会在 doSomeInstancing 的__proto__
的__proto__
的__proto__
里面查找. 然而这有一个问题: doSomeInstancing 的__proto__
的__proto__
的__proto__
不存在. 最后, 原型链上面的所有的__proto__
都被找完了, 浏览器所有已经声明了的__proto__
上都不存在这个属性,然后就得出结论,这个属性是undefined
.
执行如下代码:
输出结果为:
new 操作符的作用
当执行:
JavaScript 实际上执行的是:
可以认为在使用new 操作符创建一个实例的过程中,存在如下的中间状态:

Object.create()
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create
Object.create()
方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
。






注意d.__proto__
是undefined
,而不是null
。




注意控制台把f作为Function类的一个实例打印出来。


[未完]