文章目录
一文大白话讲清楚 :类,实例,prototype,__proto__和原型链之间的关系
1.类和实例的关系
- 我问你,你是哪个国家的人,你会说我是中国人
- 我又问你,你为什么说你是中国人,你说因为我有黄皮肤,黑眼睛,而且世世代代都是黄皮肤黑眼睛
- 这两个问题包含了类和实例的两个核心问题:
- 你和中国人之间,你是一个真真切切的可触摸的人,中国人确不是一个真真切切可触摸的人,但你又说你是中国人,那是为什么?
- 那是因为中国人是一个抽象,可以理解为一个大的归类,而你是这个归类里面的具体的一个小类
- 就好比植物和玉米的关系,植物是一种抽象,一种归类,而玉米是一个具象,是一个子类
- 这就是类和实例的关系,类就是一个抽象,实例就是通过类具象化以后的一个具象,我们一般用new来完成具象操作
- 上代码:
//我们定义一个类,中国人
class Chinese{
constructor(){
}
}
//然后我们根据中国人这个类,具象化一个具体的人,你,假设你叫张三
let zhangSan=new Chinese();
//Chinese是一个
//zhangSan是一个人,是一个被Chinese具象化的中国人
- 上述代码说明了,张三是中国人,但张三说了,我是中国人是因为我有黄皮肤黑眼睛,说明所有的中国人都有黄皮肤和黑眼睛,也就是说中国人这个抽象,这个大类里面定义了一些人中国人独有的特点,每个具象化以后得中国人都继承了这个特点,这个特点就是类的属性
- 我们在上述代码的基础上,给类加上属性
//我们定义一个类,中国人
class Chinese{
constructor(yellowSkin,blackEyes){
this.skin=yellowSkin;
this.eyes=blackEyes;
}
}
//然后我们根据中国人这个类,具象化一个具体的人,你,假设你叫张三
let zhangSan=new Chinese('yellowSkin','blackEyes');
console.log(zhangSan.skin,zhangSan.eyes)//yellowSkin blackEyes
//打印出张三的皮肤是黄色皮肤,眼睛是黑色眼睛
2. prototype和__proto__
-
在讲之前,我们接着上一个你和中国人的例子继续讲一下,我问你是哪里人,你会说你是中国人,我会说中国人这个范围太大了,再具体点是哪的,你说我是中国人,北京人
-
也就是说,你,先是北京人,再往大然后是中国人
-
即存在,你->北京人->中国人的关系
-
那么我怎么知道这个逻辑关系呢
-
这个就要靠继承
-
啥意思,好理解,你可以用DNA检测法理解,大白话,你,你爹,你爷之间的关系,你爹为啥是你爹,因为你的DNA继承自你爹,为啥你爷是你爷,因为你爹的DNA继承自你爷
-
明白了不,你继承了你爹的DNA,所以你们两就有血缘关系,你爹又继承了你爷的DNA,所以你爹和你爷就有血缘关系,所以你跟你爷也有血缘关系,这个DNA的传递就是继承,而这个血缘关系,就是原型链
-
讲完这个故事,我们可以来讲prototype和__proto__了
-
prototype和__proto__都指向一个原型对象,也就是再往上一级,你是谁的问题
-
不同的是,prototype是挂在类上的,__proto__是挂在实例上的,可以理解为引用的主体不同,如果查询类的原型对象,用类.prototype,查询对象的,用实例.proto
-
上代码
class Person{
constructor(){
}
}
let obj=new Person()
console.log(obj.__proto__===Person.prototype)//true
-
进一步理解就是,北京人这个大类里面,定义了一个prototype属性对象,里面定义了一个关于我们再往上一级是中国人的标记,如果你是由北京人这个类实例化来的人,那么你就也会有这个标记,只不过你在查这个标记的时候,不用在北京人这个类的prototype上查了,因为我在实例化你成为北京人的时候,给你添加了一个__proto__属性,这保存的就是类的prototype
-
这下讲清楚prototype和__proto__了,再来说说原型链
3. 原型链
- 第二节我们通过血缘关系已经说了原型链,就说凡是挂在北京人这个类的prototype上的属性和方法,我们被实例化的北京人都可以调用
- 上代码
class BeijingPerson{
constructor(homePalce) {
this.homePlace=homePalce
}
}
BeijingPerson.prototype.sayHomePlace=function (){
console.log(`My home place id ${this.homePlace}`)
}
let bjPerson=new BeijingPerson('Beijing')
bjPerson.sayHomePlace()//My home place id Beijing
- 而血缘关系就是,如果我继承了你,那么我的DNA就是你的DNA
- 对于类和实例,如果北京人这个类,继承了中国人这个类,那么北京人这个类的prototype就是中国人这个类的一个实例对象,而且每个由北京人这个类实例化的北京人对象的__proto__也包含中国人这个类的一个实例对象
- 我们用北京人继承自中国人,中国人继承自地球人这个逻辑关系来演示一下
//地球人
class Earth{
constructor(circle){
this.circle=circle
}
}
//中国人继承自地球人
class Chinese extends Earth{
constructor(skin,circle){
super(circle)
this.skin=skin
}
}
//北京人继承自中国人
class BeijingPerson extends Chinese{
constructor(homePlace,skin,circle) {
super(skin,circle)
this.homePlae=homePlace
}
}
//用北京人这个类实例化一个北京人
let bjPerson=new BeijingPerson('beijing','yellow','circle')
console.log(bjPerson.__proto__)//Chinese {}
//实例化后的北京人这个对象的__proto__指向北京人这个类继承的类实例化的一个对象
console.log(bjPerson.__proto__.__proto__)//Earth {}
//再往上寻找一级,实例化后的北京人这个对象的__proto__的__proto__指向北京人这个类继承的类继承的类实例化的一个对象
console.log(bjPerson.__proto__.__proto__.__proto__)//{}
//依此类推
console.log(bjPerson.__proto__===BeijingPerson.prototype)
4. prototype和constructor的关系
- 首先,每个类都有prototype和constructor,constructor每个类都有还好理解,prototype怎么说,如果定义了一个类,压根没继承任何东西,会有prototype么?答案是肯定的,会,因为我们定义的任何一个类都默认继承了Obejct类,所以默认的prototype都指向{}
- 那么prototype和constructor时间有什么联系呢
- 每个prototype里面都有一个constructor,而这个constructor指向类自己
- 啥意思,上代码
class Earth{
constructor(circle){
this.circle=circle
}
}
//我们用Earth的构造函数实例化一个对象
let obj=new Earth('circle');
console.log(obj)//Earth { circle: 'cirecle' }
//我们又说,Earth的prototype里面有一个constructor,而且指向类本身,那么就意味着
//Earth===Earth.prototype.constructor
//所以我们还可以这么实例化一个Earth,是不是点意思
let obj1=new Earth.prototype.constructor('cirecle')
console.log(obj1)//Earth { circle: 'cirecle' }
console.log(Earth===Earth.prototype.constructor)//true
- 这就开始循环了,类的prototype里面有constructor,而constructor又指向类
- 而类又有prototype
- …
- 这就是为啥大家在浏览器里面打印__proto__时,发现一层包着一层,无限打开了