如果js没有构造函数
首先不考虑构造函数这个鬼东西,当他不存在。
这个时候,创建对象的方式就是
<script type="text/javascript">var dog = {name: 'hachi',age: 3}</script>
然后在浏览器上观察该对象,可以看到该对象包含三个属性,age,name和[[Prototype]],这里的[[Prototype]]其实就是__proto__,咱也不知道为啥浏览器要显示[[Prototype]],但是确实这里只用使用dog.__proto__访问,好,下面就记住这个属性是__proto__就行了。这个属性的值是一个对象,该对象就是原型对象,类似于java中的父类的概念,只是类似。
接下来创建父对象animal,js没有父子的概念,这里应该称之为原型对象,然后让dog对象继承animal对象
<script type="text/javascript">var dog = {name: 'hachi',age: 3}var animal = {shout: function (){}}dog.__proto__ = animal</script>
继续观察dog对象,红框表示,dog对象的原型对象是animal对象,即dog对象继承了animal对象,绿框表示,animal对象继承了Object对象。是的,任何对象,最终都会继承自Object对象。
这里的__proto__属性就表示了继承关系,例如a继承了b,那么a.__proto__ == b所以,上面这个例子,我们可以判断一下是否确实有该继承关系,如下图所示,确实存在继承关系。
为了更清楚的表示原型链的链条关系,下面创建myDog对象,继承dog对象
<script type="text/javascript">var dog = {name: 'hachi',age: 3}var animal = {shout: function (){}}dog.__proto__ = animalvar myDog = {host: '张三'}myDog.__proto__ = dog</script>
可以看到myDog的原型对象的原型对象就是animal
到这里,原型链就变得超级简单了,其实就是由__proto__属性串起来的一系列对象。
但是,实际上我们不会生硬的设置__proto__属性,而是通过Object.reate函数来实现继承,如下代码所示
<script type="text/javascript">var animal = {shout: function (){}}dog = Object.create(animal)dog.name = 'hachi'dog.age = 3myDog = Object.create(dog)myDog.host = '张三'</script>
但是,加入构造函数后,事情又变麻烦了
如果js有了构造函数
那就复杂一些了。上一章提到,可以使用obj = {a:1}的形式来产生对象,也可以通过obj = Object.create(obj_proto)来产生obj对象,并且该对象的原型对象是obj_proto,其实,还有第三种方式来产生对象,那就是通过构造函数。如下代码所示,构造函数可以粗糙的认为理解为java中的类,例如这里的Animal,Dog和MyDog,都是构造函数,使用new关键字可以生成实例对象,例如这里的dog = new Dog(),就生成了构造函数Dog的一个实例对象。一个构造函数可以生成许许多多的实例对象。
<script type="text/javascript">var Animal = function (){this.shout = function (){}}var Dog = function (name,age){this.name = namethis.age = age}var MyDog = function (host){this.host = host}var animal = new Animal()var dog = new Dog('hachi', 3)var myDog = new MyDog('张三')</script>
在语句var dog = new Dog()的时候,实际上发生了什么呢?
- 创建一个空对象,作为将要返回的对象实例。
- 将这个空对象的原型,指向构造函数的
prototype
属性。也就是将Dog的prototype属性赋值给dog的__proto__属性 - 将这个空对象赋值给函数内部的
this
关键字。 - 开始执行构造函数内部的代码。例如这里的this.name = name, this.age=age
可以检查一下是否构造函数的prototype属性和实例对象的__proto__属性相等,如图所示,的确是这样。
接下来进一步研究prototype属性到底是个啥,如图所示,prototype属性其实就是一个对象,这里叫做原型对象,该原型对象包括两个属性,一个是constructor,一个是[[Prototype]]。那么问一个问题,这里的[[Prototype]]是prototype还是__proto__呢?哈哈,当然是__proto__了,因为这是一个原型对象,不是函数,只有函数才拥有prototype属性。(在js中,函数其实也是对象,但是这里为了讨论起来简单,我说的对象是指的非函数的对象,函数就直接叫做函数好了)。
如果该构造函数是自定义的函数且没有修改其prototype值,那么这里prototype对象的[[prototype]]属性其实就是Object对象,也就是说使用该构造函数生成的任何实例对象,其原型对象(即__proto__),都是Object。
constructor又是啥呢?其实就是构造函数本身,这个字段的作用就是为了让每个实例对象记住自己的构造函数。例如,构造函数Dog的原型对象的constructor属性,就是Dog,如下图所示,这里不要问为什么,反正就是这么规定的,规定prototype有个constructor属性,规定constructor属性的值就是构造函数本身。可以认为这个属性就是为了让实例对象记住自己的构造函数吧
那么如何通过构造函数来实现继承呢?
其实就是改变构造函数的prototype属性就好了,但是一定记得修改constructor属性。如下代码所示,Dog.prototype = animal将animal对象作为构造函数Dog的原型对象(prototype属性),Dog.prototype.constructor = Dog设置constructor属性的值为Dog;同理,MyDog.prototype = dog将dog对象作为构造函数MyDog的原型对象(prototype属性),MyDog.prototype.constructor = MyDog设置constructor属性的值为MyDog。
<script type="text/javascript">var Animal = function (){this.shout = function (){}}var Dog = function (name,age){this.name = namethis.age = age}var MyDog = function (host){this.host = host}var animal = new Animal()Dog.prototype = animalDog.prototype.constructor = Dogvar dog = new Dog('hachi', 3)MyDog.prototype = dogMyDog.prototype.constructor = MyDogvar myDog = new MyDog('张三')</script>
可以检查一下继承关系是否构建好,如下图所示
其实也好理解,当我执行dog = new Dog()后,构造函数Dog的prototype属性赋值给dog的__proto__属性了,而Dog.prototype就是animal;当我执行myDog = new MyDog()后,构造函数MyDog的prototype属性赋值给myDog的__proto__属性了,而MyDog.prototype就是dog,所以继承关系就成立了。
总结起来就是,非函数对象都有一个__proto__属性,该属性指向其原型对象(也就是父对象),对象之间通过__proto__属性串联起来构成原型链,而对象可以通过构造函数产生,构造函数有一个prototype属性,使用构造函数生成示例对象后,prototype属性会赋值给实例对象的__proto__属性。