Shyno
Don't be shy,no problem!

引言:每个人的知识储备和理解方式不一样,所以对于一个特定的知识点没有固定的最佳思路.就原型链这个知识点来说,网上查询了好几次了,最终还是被prototype和__proto__给搞混了.所以,为了方便记忆,我觉得采取逆向思维去理解这个知识点.

 

对象的创建

我需要一个对象,有姓名,年龄,和职业.

 let a ={name:'小茗',age:18,job:'student'}

  接下来,格局放大,我需要20个学生.方案1.手写20个学生,方案2.利用构造函数生成,因为大家都是学生,所以就不需要单独写job属性.

function Student (object){
  this.job='student'
  this.name =object.name
  this.age=object.age
 }
 let b =new Student({name:'小红',age:16})
 console.log('b',b)

 有了这个构造函数之后,你可以少写一些代码,同时,你如果仔细看打印结果你会发现它是给你标明了是利用Student来构造的 

至此,我们知道了构造函数以及他的作用.所以目前为止涉及到了两个关键词构造函数(Student)以及实例化对象(b)

接下来有个比较关键但是也不太容易理解的东西:原型对象.

js里有句话说:万物皆对象.也就是说函数也是对象,那也就算说构造函数也是对象.

不对啊?!我打印函数的时候明明是代码块,它怎么是对象呢?

我在另一篇博客里解释了js的骚操作:装箱与拆箱 ,解释了基本数据类型的变量本身在js中是以对象形式存在的.同理,构造函数其实也是同new  Function() 一致的,只是我们在使用或者取值的时候,js给拆箱了.

所以构造函数本身就是对象,而他有一个prototype属性,这个属性值本身就是一个对象,而这个对象就是原型对象.

那么,我们从头理一下.

首先,写一个构造函数,这个时候就会生成一个对象(构造函数),而这个函数有一个prototype属性,其值为原型对象,最后实例化一个对象

这里有个疑问,为啥明明构造函数本身就是一个对象了,还要搞出个原型对象来?构造函数和实例化对象一一对应不挺好吗?在回答这个问题前,我们先了解一下继承.

 

继承

我们想象一下这个场景,我们在校园中,现在要求我们对校园里的所有生物进行分类管理,我们怎么去做.假如目前有以下生物(以js的对象为例子)

let a ={name:'小茗',age:18,job:'student',species:'people',skill:'reproduction'}
 let b ={name:'小红',age:16,job:'student',species:'people',skill:'reproduction'}
 let c={name:'老张',age:36,job:'teacher',species:'people',skill:'reproduction'}
 let d={name:'乌鸦',age:1,job:'crow',species:'animal',skill:'reproduction'}
 let e={name:'老鼠',age:1,job:'mouse',species:'animal',skill:'reproduction'}

  他们有相同的共同点,也有些只是部分相同.那么让你分类怎么分?这个分类思想就是基于构造函数的优化,结合上面的Student,既然大家都有skill,且值相同,我就先搞一个构造函数

 function Biology(object){
  this.skill='reproduction'
  this.name=object.name
  this.age=object.age
  this.job=object.job
  this.species=object.species
 }

  也就是说,上面的所有我都可以用这个构造函数声明.那如果要继续分,我可以再搞两个构造函数区分动物和人类

 function Animal(object){
  this.skill='reproduction'
  this.species='animal'
  this.name=object.name
  this.age=object.age
  this.job=object.job

 }
 function people(object){
  this.skill='reproduction'
  this.species='people'
  this.name=object.name
  this.age=object.age
  this.job=object.job
 }

可实际上这两个构造函数都有"this.skill='reproduction'",所以,Biology是不是可以不要了?小了,格局小了,假如等下我又得让你去管动物园的所有生物,你是不是又得重新搞?那不去掉不是浪费了吗?

我们就可以利用起来Biology

function Biology(object){
  this.skill='reproduction'
  // this.name=object.name
  // this.age=object.age
  // this.job=object.job
  // this.species=object.species
 }
 function Animal(object){
  Biology.apply(this)
  this.species='animal'
  this.name=object.name
  this.age=object.age
  this.job=object.job
 }
 function People(object){
  Biology.apply(this)
  this.species='people'
  this.name=object.name
  this.age=object.age
  this.job=object.job
 }
 let aNew =new People({name:'小茗',age:18,job:'student'}) console.log('222',aNew)

 其中, Biology只描述生物共同的特征,具有繁殖功能,而 Animal和People描述其具体特征,于是Biology可以变得简洁.以此类推,

 function Biology(object){
  this.skill='reproduction'
 }
 function Animal(object){
  Biology.apply(this)
  this.species='animal'
 }
 function People(object){
  Biology.apply(this)
  this.species='people'

 }
 function Student(object){
  People.apply(this)
  this.species='people'
  this.job='student'
  this.name=object.name
  this.age=object.age
 }
 function Teacher(object){
  People.apply(this)
  this.species='people'
  this.job='teacher'
  this.name=object.name
  this.age=object.age
 }
 function Crow(object){
  Animal.apply(this)
  this.species='people'
  this.job='teacher'
  this.name=object.name
  this.age=object.age
 }
 function Mouse(object){
  Animal.apply(this)
  this.species='people'
  this.job='teacher'
  this.name=object.name
  this.age=object.age
 }
 let aNew =new Student({name:'小茗',age:18})
 let bNew =new Student({name:'小红',age:16})
 let cNew =new Teacher({name:'老张',age:36})
 let dNew =new Student({name:'乌鸦',age:1})
 let eNew =new Mouse({name:'老鼠',age:1})
 console.log('222',aNew,bNew,cNew,dNew,eNew)

  

这样分就很清晰了,那假如我现在又看到个大象呢?生物和动物的特征都有,继承来就行.所以我就可以少写几行代码.同时分类也清晰.

那么,既然我要继承,我得知道从哪儿继承的吧?目前代码看下来好像就是从构造函数继承下来的,可js认为,构造函数就是纯粹用于构造的,他是干净的.于是,它找了个背锅侠,就是构造函数的prototype属性.也就是说,对于实例化对象来说,你不能找到我构造函数身上来,我把你构造出来了就行.你想追根溯源,那就去找我的prototype属性去.但是prototype属性作为一个背锅侠,他替谁背锅他自己得记得.

所以最终就是构造函数构造单方面构造了实例化对象,实例化对象也单方面跟原型对象触,但是构造函数和实例化对象却是双向沟通.

 

为什么出现这种情况?

我的理解是,构造函数虽然创造了实例化对象,但把其prototype作为原型去跟实例化对象对接,如果需要额外的属性对接就由原型去承受,保证构造函数的单纯性.同时原型和构造函数是一一对应的,但是原型和实例化并不是一一对应的,所以实例化对象能找到原型,但原型不需要构造函数到底创造了几个实例化对象.

记忆点:prototype原单词的意思就是原型,所以其是构造函数的原型,而原型也是有构造函数constructor(构造的).而__proto__看着单词短小,卑微无助的感觉,这是可怜的实例化对象找妈妈时候,也就是实例化对象去找原型.

 console.log('222',aNew.__proto__===Student.prototype,Student===Student.prototype.constructor)

 

原型链

在继承的说明里,我们写了,对象的生成有时候是流水线上一层层构建出来的,每层提供其属性给对象,通关继承关系,最终让一个对象拥有了很多属性.比如"小茗"他是个学生,但是实际上学生构造函数并没有给"小茗"species属性,那它有没有species呢?

有.因为当我调用aNew.species 时候,js会去看Student.prototype有没有?没有.可是Student虽没有.它的爸爸People.prototype有啊,所以我们可以通过Student.prototype去找,也就是说,当我们继承的时候,这两个prototype也关联上了,于是People.prototype成了Student.prototype的原型,也就是说,我们也可以用__proto__去找原型.,故此,当一层层继承的时候,原型也互相关联了起来,形成了原型链,而最终的实例化对象就可以拿到一整个原型链上的属性.

 

所以呢?原型链的出现是由于构造函数的继承模式产生的,其目的是细化构造函数同时方便管理和复用.实例化的对象虽然由构造函数生成,但其属性在原型上继承,,或者原型的原型.

 

new关键词

由此,我们考虑一下,当我们new一个关键词的时候都发生了什么.比如

 let aNew =new Student({name:'小茗',age:18})

  1.首先,创建了一个对象{name:'小茗',age:18}

       2.然后aNew.__proto__=Student.prototype,把原型指向构造函数的原型.

 

 let aNew =new Student({name:'小茗',age:18})
 let bNew = {name:'小红',age:16}

 这两种方式有啥区别

实际上

 let bNew = {name:'小红',age:16}
就是
 let bNew =new Object( {name:'小红',age:16})

 也就是说,所有的对象都会具有Object.prototype的属性.为了解决这个问题,Object提供了一个方法,若Object.creat(null) ,即在创建的时候把对象的原型换成底层的null,那这个对象就不具备任何属性.

 

posted on 2022-08-08 20:32  Shyno  阅读(28)  评论(0编辑  收藏  举报