原型链的理解是javascript中的老大难题,对于初学者非常不友好,甚至对于很多工作两三年的程序员来说,也没能真正理解javascript中的原型链的精妙设计!
我们想要理解原型链,首先我们先要跳出原型链的直接讲解,
因为javascript中的原型链其实涉及到一个javascript的本源设计
,就是我们先要探讨一下为什么javascript要设计原型链,并且javacript这门语言其实是没有原型链也能完美执行并且保持图灵完备的。
首先我们看一下一个风靡世界的游戏英雄联盟LOL的英雄制作过程我们用javascrip描述以下英雄薇恩(vn)。
此时我们用javascript中的对象来模拟英雄联盟中的英雄的功能和属性。
varhero={heroName:vn,heroLife:95%,Q(){//闪避突袭复杂处理函数省略console.log代替过程console.log(闪避突袭薇恩翻滚到合适位置,为下次攻击做好准备。她的下次攻击造成额外伤害。)},W(){//圣银弩箭复杂处理函数省略console.log代替过程console.log(闪避突袭圣银弩箭薇恩用稀有金bai制作箭弩,让邪恶敌人中毒。对同一目标的第3次攻击或技能施放会对其造成额外真实伤害,数值相当于目标最大生命值一定百分比。(对怪物最多造成伤害)},E(){//恶魔审判复杂处理函数省略console.log代替过程console.log(恶魔审判薇恩从背后拿出重弩,朝目标发射巨箭,对其造成伤害并击退他们。如果在击退过程中碰撞墙或者地形边缘,目标将受到额外伤害并晕眩。)},R(){//决战时刻复杂处理函数省略console.log代替过程console.log(决战时刻誓死一战。她获得额外攻击力,在闪避突袭期间潜行,暗夜猎手(被动)生效时加速效果提高3倍。)}}
游戏玩法界面是这样的,以下是十个英雄薇恩
十个一样的英雄同场作战,甚至私服mod可以容纳更多的英雄,我们用代码来描述一下
varheros=[]varherofor(vari=0;i10;i++){varheroVn={heroName:vn,heroLife:95%,Q(){//闪避突袭复杂处理函数省略console.log代替过程console.log(闪避突袭薇恩翻滚到合适位置,为下次攻击做好准备。她的下次攻击造成额外伤害。)},W(){//圣银弩箭复杂处理函数省略console.log代替过程console.log(闪避突袭圣银弩箭薇恩用稀有金bai制作箭弩,让邪恶敌人中毒。对同一目标的第3次攻击或技能施放会对其造成额外真实伤害,数值相当于目标最大生命值一定百分比。(对怪物最多造成伤害)},E(){//恶魔审判复杂处理函数省略console.log代替过程console.log(恶魔审判薇恩从背后拿出重弩,朝目标发射巨箭,对其造成伤害并击退他们。如果在击退过程中碰撞墙或者地形边缘,目标将受到额外伤害并晕眩。)},R(){//决战时刻复杂处理函数省略console.log代替过程console.log(决战时刻誓死一战。她获得额外攻击力,在闪避突袭期间潜行,暗夜猎手(被动)生效时加速效果提高3倍。)}}heros.push(heroVn)}//制造出了十个英雄vnconsole.log(heros)
这样设计其实是没问题的,英雄的技能组成,以及思路都是没问题,但是这样设计游戏人物有j几个重大的缺陷!!!!
这种代码存在一个巨大的缺陷不利于商业化,可以观察到这十个英雄唯一在游戏中不同的只是heroLife(玩家受到的伤害不用生命值显示不同,会根据玩家受到的伤害不同而减少,每个玩家的生命值在游戏中都是动态变化的。
heroName(英雄名字)以及英雄的四个技能QWER十个英雄,都是一样的。没必要重复创造十次,去占据内存,白白消耗用户的机器性能。
只有heroLife英雄的血量根据游戏的过程受到的伤害不同而动态改变,那样就得想一个办法,让这个英雄共用heroName以及QWER以达到节约内存的效果!
我们将代码进行改进
//共有属性varvn=function(){}vn.prototype={heroName:vn,Q(){//闪避突袭复杂处理函数省略console.log代替过程console.log(闪避突袭薇恩翻滚到合适位置,为下次攻击做好准备。她的下次攻击造成额外伤害。)},W(){//圣银弩箭复杂处理函数省略console.log代替过程console.log(闪避突袭圣银弩箭薇恩用稀有金bai制作箭弩,让邪恶敌人中毒。对同一目标的第3次攻击或技能施放会对其造成额外真实伤害,数值相当于目标最大生命值一定百分比。(对怪物最多造成伤害)},E(){//恶魔审判复杂处理函数省略console.log代替过程console.log(恶魔审判薇恩从背后拿出重弩,朝目标发射巨箭,对其造成伤害并击退他们。如果在击退过程中碰撞墙或者地形边缘,目标将受到额外伤害并晕眩。)},R(){//决战时刻复杂处理函数省略console.log代替过程console.log(决战时刻誓死一战。她获得额外攻击力,在闪避突袭期间潜行,暗夜猎手(被动)生效时加速效果提高3倍。)}}varheros=[]varvn1for(vari=0;i10;i++){vn1={heroLife:初始后续会根据每个英雄受到伤害不同动态变化}vn1.__proto__=vn.prototype}
好了,通过以上的改进,我们就达到我们的目的,英雄的生命值属于,每一个唯一id的英雄,但是英雄的共有属性不在占据独有的内存空间而是去指向共有的英雄属性不在白白浪费内存空间了!这也是原型链设计的最终目的以及初衷!
但是这样的写法,很拗口也很难理解,JS的官方组织也有意去弱化这个设计,让不知道这个设计的程序员也可以很好的使用这个功能去节约内存,然后就有了new操作符!
new操作符实际上只做了四步
新生成一个对象
链接到原型
绑定this
返回新的对象
以下代码演示,被隐藏的这一步
var临时对象={}临时对象.__proo__=vn.prototype//this赋值和指向临时对象.heroLife=heroLifereturn回去这个临时对象
你可以理解成:是把上面的详细代码的过程给你隐藏,直接让你得到一个绑定好原型链不占用内存的一个新的对象。此例中是英雄vn。
而实际上的原型链是一个为了不占用内存空间而寻祖的过程我们再通过一幅图来很好的理解原型链!
上图可以看到,孙悟空、六耳猕猴用的技能实际上是菩提老祖创造出来的,他们的共有属性是__proto__连接,对菩提老祖是一种寻祖的过程。
至于原型链的尽头为什么是null,这里就要设计到一种哲学思想了,
宇宙大爆炸之前宇宙也是null,盘古开天地以来天地也是null,所以原型链也是javascript的世界观!
总结
原型和原型链是JS实现继承的一种模型。
原型链实际上是为了节约内存的一种寻祖现象
原型链是javascript的精妙设计也是javascript的世界观