原型链的理解(全面)
创建一个实例
使用构造函数创建一个实例
function Star(age,name) {
this.age = age
this.name = name
this.sing = function(){
console.log("我可以唱歌")
}
}
let ldh = new Star(35,"刘德华")
console.log(ldh)//35,刘德华
理解构造函数里面的内容,构造函数里面是有静态成员和实例成员的
-
实例成员就是构造函数内部通过this添加的成员,在这里的sing、name和age就是实例成员,这个只能通过实例对象来访问,不能通过构造函数访问
console.log(Star.sing)//undefind -
静态成员,就是直接在函数本身上面添加的成员,只能通过构造函数访问
Star.sex = “男”//这个就是静态成员
console.log(Star.sex)//男
console.log(ldh.sex)//undefined
虽然构造函数挺不错的,但是存在一个严重的内存浪费的问题,当我们使用构造函数创建多个实例的时候,如果这个函数里面有多个静态成员的函数,那么我们每一次创建实例对象,构造函数下的方法都会重新开辟一个内存空间去存放这些方法。
但是明明这些方法都是一个构造函数里面的,干嘛要这么麻烦,还要占用内存,所以出现了原型
构造函数里的东西
prototype
构造函数通过原型分配的函数是所有对象所共享的,js规定,每一个构造函数都有一个prototype属性,指向另外一个对象。注意这个prototype既是一个对象,这个对象的所有属性和方法,都是被构造函数所拥有
我们可以把那些不变的方法,直接定义在prototype对象上这样所有的实例都可以共享这个方法
所以上面的方法的改进应该是这样
function Star(age,name) {
this.age = age
this.name = name
}
Star.prototype.sing = function(){
console.log("我会唱歌")
}
let ldh = new Star(35,"刘德华")
ldh.sing//我会唱歌
因为prototype是一个对象,所以我们称构造函数的prototype为原型对象
__proto__
对象都会有一个属性__proto__,指向构造函数的prototype原型对象,之所以我们对象可以使用构造函数prototype原型对象的属性和方法,就是因为有__proto__原型的存在
我们打印这两个看看里面有什么
console.log(ldh)
console.dir(Star)
结果如下
我们可以看到ldh这个实例之中是有__proto__的,而构造函数也是有prototype的,那既然说__proto__指向构造函数的prototype对象,那么我们来看看是不是真的
console.log(ldh.__proto === Star.prototype)//true
结果自然是正确的
那既然实例的对象原型(ldh.proto)和构造函数的原型对象(Star.prototype)是一样的,那么我们就可以解释的通为什么实例可以直接使用构造函数上面的方法了
constructor构造函数
我们先打印一点东西看看
console.log(ldh.__proto__)
console.dir(Star.prototype)
结果如下
我们可以看到两个里面都有一个constructor对象,那么这个是什么玩意呢
其实这个我们还是称之为构造函数,因为它指向构造函数的本身,
我们可以试试看是不是这样
console.log(ldh.__proto__.constructor)
结果是和预期的一样的
所以constructor主要用于记录该对象引用于哪个构造函数,他可以让原型对象指向原来的构造函数
怎么用呢,可以看看这种情况
现在我们的构造函数有很多的方法,但是我们一直用.方法的形式好像有点麻烦,干脆就直接使用对象存储吧
function Star(age, name) {
this.age = age
this.name = name
}
Star.prototype = {
sing:function(){
console.log("我会唱歌")
},
movie:function(){
console.log("我会演电影")
}
}
let ldh = new Star(35, "刘德华")
console.log(ldh.__proto__.constructor)
结果如下
好像指向的就不是原来Star这个构造函数了,这是怎么回事
原来,Star.prototype本身就是一个对象,你给Star.prototype赋值一个对象,那就会把原来prototype上面的对象给覆盖了,所以这个时候就可以使用constructor了
function Star(age, name) {
this.age = age
this.name = name
}
Star.prototype = {
constructon:Star
sing:function(){
console.log("我会唱歌")
},
movie:function(){
console.log("我会演电影")
}
}
let ldh = new Star(35, "刘德华")
console.log(ldh.__proto__.constructor)
只需要在Star.prototype里面重新指向这个构造函数就解决问题了
三者的关系
看了这么多,我们应该可以理解构造函数、实例、原型对象之间的关系了吧,只有理解了这些,才能更快的理解原型链的内容
我们可以只用一张图表示他们的关系
下面开始原型链的理解
原型链
直接看图先:
讲解一下图里面的内容
- 首先说好了的每一个实例都有__proto__,而这个就指向的是构造函数的对象原型,所以ldh.__proto__和Star.prototype是等价的,即ldh.proto`能够只会Star这个构造函数呢,本质上就是通过Star.prototype指回去的。
- 那么我们就会想到Star.prototype的__proto__指向的会是谁呢,一打印就能看的出指向的是Object.prototype,所以不用想也知道他的构造函数肯定是Object了
- 那Object.prototype还有没有__proto__,打印一看就知道是为null,所以我们知道原型的终点就是null
每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么假如我们让原型对象等于另一个类型的实例,结果会怎样?显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立。如此层层递进,就构成了实例与原型的链条。这就是所谓的原型链的基本概念
了解了原型链,我们再来了解最后一个概念
js的成员查找机制
- 当访问一个对象的属性或者方法的时候,首先查找这个对象自身有没有这个属性
- 如果没有就去找他的原型,也就是__proto__指向的prototype原型对象
- 如果还没有就去查找原型对象的原型,即Object的原型对象
- 以此类推一直找到null为止
所以我们经常可以使用一些对象上面的方法就是因为如此
function Star(age, name) {
this.age = age
this.name = name
}
Star.prototype.sing =function(){
console.log("我会唱歌")
}
let ldh = new Star(35, "刘德华")
Object.prototype.sex="男"
console.log(ldh.sex)//男
这个是可以输出Object.prototype上面的sex属性的
我们还知道Object.prototype上面有一个toString方法,但是我们自己写的Star构造函数和ldh对象里面是都没有这个方法的,那么我们还可以使用这个方法吗
测试一下不就知道了
function Star(age, name) {
this.age = age
this.name = name
}
Star.prototype.sing =function(){
console.log("我会唱歌")
}
let ldh = new Star(35, "刘德华")
console.log(ldh.toString())//[object Object]
结果依然是正确的,所以这也验证了我们上面的规则