这是你掉的“原型与原型链”吗?快捡起来吧!》(^o^)《

481 阅读7分钟

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情

一.从一个小例子到原型链

  • 豆豆家是一个商业世家,豆豆的爷爷创建了世界上最大的商业中心
  • 豆豆的爸爸创建了世界上最大的餐饮连锁店
  • 而豆豆开建了豆豆爸和豆豆爷都没有创建的电竞城
  • 但是,豆豆爸没有告诉豆豆爷他创建的餐饮连锁店,豆豆也没有告诉豆豆爸他创建的店
  • 在豆豆想要带朋友去玩免费电竞的时候,想起自己有建一个,他就会去自己的电竞城
  • 在豆豆想带朋友去免费吃饭的时侯,想到自己没有建餐厅,就会去找豆豆爸问,豆豆爸想到自己有餐厅,就让豆豆去这个地方
  • 在豆豆想要去免费商业城shopping的时候,想到自己没有建,他就会去问豆豆爸哪里有,豆豆爸想到自己没有建,就会去问豆豆爷,豆豆爷想到自己有,就会让豆豆去他的商业中心
  • 在豆豆去完爷爷的商业中心,发现爷爷商场的衣服一点也不适合自己还有朋友,所以他建了自己的时尚服装店,所以下次去买衣服只需要去自己的店就可以了
  • 有一天,他的朋友想去海洋馆,豆豆想到自己没有,问了豆豆爸,豆豆爸也无,然后问豆豆爷,豆豆爷也无,所以告诉豆豆没有

二.用代码模拟一下这个小例子

//爷爷
let GrandFather = function(){
    this.shopping= function(){
        console.log("免费购物");
    }
}
let grandFather = new GrandFather();
//爸爸
let Dad = function(){
    this.restaurant = function(){
        console.log("免费吃饭");
    }
}
let dad = new Dad();
//儿子
let Son = function () {
    this.Gaming = function () {
        console.log("免费游戏");
    }
}
//此处代码暂时不说明
// 形成继承关系(头)
Dad.prototype = new GrandFather();
Dad.prototype.constructor = Dad;
Son.prototype = new Dad();
Son.prototype.constructor = Son;
// 形成继承关系(尾)
let son = new Son();
son.Gaming();//豆豆去玩电竞
son.restaurant();//豆豆去吃饭
son.shopping();//豆豆去购物
Son.prototype.shopping = function(){
    console.log("免费时尚购物");
}
son.shopping();//豆豆创建了一个新的商店后去购物

三.原型的一般规则

  1. 构造函数有其对应的显式原型的,该显示原型有一个属性constructor指向构造函数本身
    • 此处以豆豆为例,其构造函数Son()有一个对应的显示原型Son.prototype,这个原型有一个属性constructor指向Son()这个构造函数,即: Son.prototype.constructor === Son
    • 所以 let son = new Son() 和 let son = new Son.prototype.constructor() 是一样的
  2. 实例对象的隐式原型属性__proto__都对应其构造函数的原型(Object例外)
    • 依旧以豆豆为例,那么豆豆的实例son的隐式原型属性就和其构造函数Son()的隐式原型Son.prototype相等,即
      son.__proto__===Son.prototype
      
    • 这里再聊一些特殊的东东去加深这个,经过测试可以发现所有构造函数的隐形原型属性都是Function.prototype,也就是说所有的构造函数都是Function()的实例(包括它自己),即:
        console.log(Son.__proto__===Function.prototype)//true
        console.log(Dad.__proto__===Function.prototype)//true
        console.log(GrandFather.__proto__===Function.prototype)//true
        console.log(Function.__proto__===Function.prototype)//true
    
  3. 构造函数的原型的隐式原型属性默认是Object的实例
    • 依旧以豆豆为例,那么在豆豆Son()这个构造函数创建的时候,就有:
    • Son.prototype = new Object()
    • 同时需要注意的是,Object.prototype = null,这是为了防止原型链无限循环设计的

四.关于原型链

首先,一个实例对象在调用其方法属性的时候,先会从自身寻找属性方法,如

son.say = function(){
  console.log("豆豆会说话");
}
son.say();//豆豆会说话

我们在实例Son对象后,在它的实例son上添加了新的方法say(),那么我们就可以去调用它,同时我们也知道son的构造函数中有Gaming()方法,也是可以直接调用的,这是我们在未学习原型和原型链知识前就知道的。而回到我们刚开始的例子中,我们或者会疑惑,为什么son这个实例可以使用Dad对象和Grandfather对象的方法呢?而答案就在我们未解释的代码之上,现在让我们来讲解它:

  • 当一个对象访问其属性时,会先从自身的属性列表上寻找,如果没有找到,就会通过其隐式原型属性到其原型上寻找,循环(前面说到的:对象原型是某个对象的实例,实例对象都有隐式原型属性),直至找到,或者 未找到返回undefined(调用方法就返回什么什么不是一个方法)。

这里建议回去看开篇的那个小故事再理解一下,然后以豆豆实例son要去自家免费3d体验馆Experience3d()为例,

  1. 豆豆会先查看自身的属性方法:say()、Gaming(),没有Experience3d()方法,
  2. 然后就到其隐式原型上查找,Son.prototype,即Dad对象的实例,拥有所有的Dad对象的方法,restaurant(),以及添加在Son原型即Dad对象实例上的方法(就是下面这个代码):shopping(),也没有想要的方法,
    Son.prototype.shopping = function(){
         console.log("免费时尚购物");
    }
    
  3. 这个时候就会到Son原型的隐式原型,也就是Dad对象实例的隐式原型即Dad.prototype上查找,Dad.prototype是Grandfather对象的实例对象,拥有所有的Grandfather对象的属性和方法,即shopping(),也没有找到,
  4. 然后就会去Dad.prototype也就是Grandfather对象的实例的隐式原型即Grandfather.prototype上查找,Grandfather.prototype默认是Object对象的实例,拥有所有Object对象的方法,如toString()等,但是也没有想要的方法Experience3d(),
  5. 然后就会去查找Grandfather.prototype也就是Object对象实例的隐式原型属性,即null,也就找不到这个方法,返回undefined,调用它就会报错“Experience3d不是一个方法”(因为它是undefined)
  • 而这个查找的过程就是链式的,我们就将其称为原型链。(建议自己推演一下上面那个过程,如果有疑问的地方,回去再查看一下“原型的一般规律”)

这里放个丑丑的图:

捕获.PNG

五.了解原型链的内部

这里放个图,其实就是讲一下设计者的最初设计:

捕获2.PNG

图中可以看到一些有意思的点:
1. Object()构造函数的隐式原型是Function.prototype,而Function.prototype的隐式原型是Object.prototype
2. Function()构造函数是自己的实例

六. 原型链的使用

1. 形成继承关系

这里继续使用上面的代码

//爷爷
let GrandFather = function(){
    this.shopping= function(){
        console.log("免费购物");
    }
}
let grandFather = new GrandFather();
//爸爸
let Dad = function(){
    this.restaurant = function(){
        console.log("免费吃饭");
    }
}
let dad = new Dad();
//儿子
let Son = function () {
    this.Gaming = function () {
        console.log("免费游戏");
    }
}
// 形成继承关系
Dad.prototype = new GrandFather();
Dad.prototype.constructor = Dad;
Son.prototype = new Dad();
Son.prototype.constructor = Son;

通过修改构造函数原型的指向,再修改构造函数原型的构造函数(不修改会指向与原型相同的那个对象的构造函数,如Son.prototype.constructor = Son若不加,那么Son.prototype.constructor===Dad),就可以形成继承关系。

2. 实例共享方法

再次看向我们之前的代码:

Son.prototype.shopping = function(){
    console.log("免费时尚购物");
}

这里我们向Son的原型新增了一个方法shopping(),那么son的实例均可以使用这个shopping()方法,但是,这和直接在Son的构造函数里加shopping()方法有什么区别呢?区别在于:

使用Son创建的shopping()方法会在每个Son()构造函数实例创建一个shopping()方法,而由构造函数原型所创建的shopping()只会创建一次,被所有Son()构造函数所创建的实例共享

这个实际上也很容易理解,因为Son.prototype = new Dad();也就是说Son.prototype指向一个对象实例,那么这个实例是一次性创造的,而对他添加方法,就是实例对象添加方法,而每个Son对象实例通过原型链访问的都是同一个Dad实例,那shopping()方法肯定是同一个。 即:

let son2 = new Son()//son2就当作是豆豆弟弟吧
console.log(son.__proto__===son2.__proto__)//true

总之,通过这个方法实现实例间共享方法,可以节省内存空间

七. 总结

放一下上面用到的全部代码吧,如果有疑问,可以选择带着怀疑的态度去验证,代码也最好自己试试哦!(大佬除外)
我觉得我这篇文章还是不错的吧,可以的话给我点个赞吧,球球了!球球了!