JavaScript深入系列之原型与原型链
前言
每次遇到新的概念,我们可能会想到三个问题,它是什么呢(what)?为什么会出现呢(why)?又能处理什么呢(how)?这次我们来看看原型与原型链这个概念,要处理这个3W(what, why, how),首先得从js的面向对象说起…
此文需要准备好电脑,大脑,一杯咖啡面向对象
什么是面向对象?应该不需要我多说吧,任何编程语言都有面向对象,而后它们都有类(class) 的概念,但在ECMAScript(以下用es表示)中却没有,除了es6之外(其实上是语法糖,这样是为了更像面向对象编程的语法而已),但不代表由于es6有了类,我们即可以把原型与原型链的概念一笔带过,由于es6一般是在es5的基础上优化来的,而且现在面试对js的基础扎实要求比较高,上次被问到过有几种继承方法,答复没完全正确…?
那么问题来了,既然没有类的概念,那es又是如何实现的呢?起初是用工厂模式实现,这个又是什么玩意呢?继续看下面?
工厂模式
正如字面的意思,就拿制造娃娃(Doll)的?来说吧,工厂要制造一个娃娃的过程:首先制造娃娃的对象(new Object()),而后赋给娃娃的属性,比方颜色,多高,多重,最后就制造出来,代码如下:
function createDoll(color,height,weight){ //制造工厂模式的函数 let o = new Object();//创立一个娃娃的对象 o.color = color;//颜色 o.height = height;//多高 o.weight = weight;//多重 o.sayColor = function(){//报告颜色 alert(o.color); } return o;}//为了显示是工厂模式,变量名后面加F(工厂:Factory)let dollF1 = createDoll('orange',20,10);//要制造一个娃娃,就调用上面的函数let dollF2 = createDoll('red',30,10);//同上把一个对象的所有属性和方法(所谓本身的信息)封装在一个工厂模式的函数,这样就能制造多个对象
好处:
减少大量重复的代码
缺点:
无法识别对象的类型问题,换句话说,就是无法判断一个对象来自于哪个函数呢?这个稍后再说,正是由于这个缺点,所以才会出现构造函数模式~
构造函数模式
什么是构造函数呢?写法其实和普通函数差不多的,但只需被new调用就称为构造函数,代码如下:
function Doll(color,height,weight){//原本是普通函数 this.color = color; this.height = height; this.weight = weight; this.sayColor = function(){ alert(this.color); }}//为了表示是构造函数模式,变量名后面加Clet dollC1 = new Doll('orange',20,10);//用new调用了,上面的函数就称为构造函数let dollC2 = new Doll('red',30,10);为了更好的识别构造函数,函数名应该以一个大写字母开头,比方上面的大写字母D,而后与工厂模式相比,去掉创立对象( let o = new Object();),直接把属性和方法赋给this对象,去掉return返回语句。
这个有啥优点,之前提到过工厂模式无法识别对象的类型问题,我们先看看调用构造函数和调用工厂模式的打印结果
我们不光要看文章,还要动手打code,这样比较容易了解~调用工厂模式的结果如下:
调用工厂函数的结果
调用构造函数的结果如下:
调用构造函数的结果
我们可以发现,调用构造函数比工厂模式多了一个constructor属性,这个属性指向Doll,为了证实这两个是相等,我们可以打印出看看:
dollC1.constructor === Doll; //true//而后我们再来看看工厂模式的又是如何呢dollF1.constructor === createDoll; //false,由于constructor属性找不到creteDoll,所以无法判断来自于createDoll函数,这就是工厂模式的缺点为什么非要判断对象类型,别急,这个要说的起因很长,要一个一个解释,
就好比说吃美食,要慢慢咬嚼,才能知道美食是什么味道,相反吃的太快,就说不出味道的,也就没办法向别人(面试官或者者写代码)解释这个本质~
这个constructor属性可以用来标识对象类型,但小红书推荐用instanceof来判断,这个是表示可以是Object的实例,也可以是构造函数的实例,看看上面调用工厂模式和构造函数的图片,工厂模式中的dollF1__proto__下面有一个constructor,指向Object;而构造函数中的dollC1__proto__下面有两个constructor,分别指向Object和Doll,而后我们运用instanceof来判断:
//构造函数模式dollC1 instanceof Doll; //truedollC1 instanceof Object; //true//工厂模式dollF1 instanceof createDoll; //falsedollF1 instanceof Object; //false所以这就是工厂模式和构造函数模式的优劣比,至于为什么会有指向Object,但凡创立的实例对象都有Object构造函数,至于详细,可能比较长,有个知乎大佬写的比较有意思,推荐看看JavaScript世界万物诞生记;
然而不要太天真了,任何事物绝对没有完美的,构造函数也是如此的,不然我就不会讲原型与原型链,那么构造函数有什么缺点呢?我们先看构造函数中的sayColor方法,这个方法在多个实例对象上应该是一样,然而看下面?的代码:
dollC1.sayColor === dollC2.sayColor; //false结果打印出false,哈?为什么呢?调用构造函数后,会生成不同作用域的多个实例,相当于开拓多个实例的内存,拥有自己的属性可以了解的,但拥有自己的方法这就没必要,就好比说两个人要去旅行,需要准备东西,因为卫生问题,带上自己的毛巾(属性)可以了解的,而后你和朋友都要带上沐浴液,带的越多,提的就越重,换个角度想,假如不带自己的沐浴液,就借朋友的沐浴液来用,是不是可以减少自己的容量呢?在构造函数也是一样的道理,带自己的属性和方法越多,开拓的内存就越大,所以出现原型模式这个概念(终于到这一步了?)
原型模式(也可以叫原型)
什么是原型模式呢?原型模式也可以这么叫共享模式(个人了解哈),这几年不是很流行共享嘛?共享单车,共享充电宝,共享汽车等等,一个单车可以被所有人共用,相当于一个原型模式可以被所有的实例共用,区别在于单车要花钱哈~
之前讲到的构造函数的属性和方法,这次在原型模式中,把属性和方法移到构造函数的原型对象,用prototype实现:
function Doll(){};Doll.prototype.color='orange';Doll.prototype.height=12;Doll.prototype.weight=6;Doll.prototype.sayColor=function(){ console.log(this.color);}//为了表示是原型模式,变量名后面加Plet dollP1 = new Doll();dollP1.sayColor(); //orange;let dollP2 = new Doll();这就是原型模式,之前说的构造函数有个缺点的,多个实例对象的方法不一致,这次我们来看看多个实例对象的方法在原型模式又是如何呢?
dollP1.sayColor === dollP2.sayColor; //true一样的耶,就能省下好多内存的,关系图如下:
原型模式
而后我们再看看dollP1的打印结果,比照上面的关系图:
dollP1
dollP1.proto指向Doll的原型对象(Doll.prototype),假如要读取color的值,就会沿着这个方向去原型对象里面找,找到color,就返回color的值,找不到的话,则继续往上Object的原型对象,还是找不到的话,则会返回”undefined”,下面我们试试dollP1的color和nama属性:
dollP1.color; //orangedollP1.name; //undefined然而假如我要给dollP1这个实例添加自己的属性,比方color,那直接在dollP1上添加这个属性就好了,代码如下:
dollP1.color='red';dollP1.color; //red关系图如下:
实例对象添加一个属性
实例上多了一个属性,这样读取color的值,首先会在自己的实例中搜索,找到了,就返回‘red’,就不会再往上原型对象里搜索。
注意:在实例对象上即便没赋值,比方undefined,也依然会返回undefined,也就是说实例对象只需有该属性,不论有没有赋值还是null,也会直接返回该属性的值看起来很完美的,先别这么想的,我们再来试试引用类型,比方数组,在原型对象上加一个数组arr的属性:
Doll.prototype.arr=[1,2,3];//而后在实例对象dollP1修改原型对象的数组,会发生什么呢?dollP1.arr.push(4); //4,返回数组的长度Doll.prototype.arr; //[1, 2, 3, 4],唔,原型对象的arr也发生变化dollP2.arr; //[1, 2, 3, 4]肯定要动手打code,不然就没办法体会到呢就由于实例对象修改原型对象的数组属性,结果实例对象,原型对象都被改变了,这样原型对象就失去自己的准则,想看看假如多个实例对象调用一个原型对象,假如修改原型对象的数组属性,多个实例对象就都拥有同样的值,那创立多个实例对象有什么意义呢?所以这也就是原型模式的缺点,所以出现组合模式~
组合模式
组合模式又是什么呢?就是利用构造函数模式和原型模式的各自特点组合成一个模式,构造函数模式特点是能创立独立的实例,而原型模式的特点是能被共享,所以就把属性放进构造函数模式,而方法则放进原型模式,代码如下:
function Doll(color,height,weight){ this.color = color; this.height = height; this.weight = weight; this.arr=[1,2,3];};Doll.prototype.sayColor=function(){ console.log(this.color);}let doll1 = new Doll('orange',14,6);dollP1.sayColor(); //orange;let doll2 = new Doll('red',12,4);doll1.arr.push(4); //4doll1.arr; //[1,2,3,4]doll2.arr; //[1,2,3];doll1.sayColor === coll2.sayColor; //true这样每个实例就拥有自己属性的副本,还能共享原型对象的方法,就能省下很多内存的。
这下我们处理了原型的3W问题,而后面向对象还有一个特点的,那就是继承,在es中又是通过什么方式处理继承的问题?答案就是原型链,看下面?
原型链
什么是原型链呢?我们可以把这个概念拆分成“原型”+“链”,原型,就是之前讲到过的原型,而链呢,就是在原型模式上产生指向的链条,之前我们讲了三个构造函数,原型对象,实例对象,对吧?如果再创立另一个构造函数,而后让这个新的构造函数的原型对象等于原来构造函数的实例化对象?怎样了解的?比方说创立一个娃娃(目前只能制造人偶娃娃,Doll默认为人偶娃娃),现在有一个新的需求,要制造动物娃娃,动物娃娃和人偶娃娃差不多,可以直接借助Doll构造函数来实例化,说白就是继承Doll的属性和方法,不需要自己再创立同样的属性,代码如下:
function Doll(color,height,weight){ //默认人偶娃娃 this.color = color; this.height = height; this.weight = weight;};Doll.prototype.sayColor=function(){ console.log(this.color);}//制造动物娃娃的构造函数function AnimateDoll(name){ this.name = name;}//借助Doll构造函数来实例化AnimateDoll的原型对象AnimateDoll.prototype = new Doll();AnimateDoll.prototype.sayName=function(){ return this.name;}let animate1=new AnimateDoll('pig');animate1.name; //piganimate1.sayName(); //piganimate1.color; //undefined, 由于没传值,这个稍后再说呢~animate1.sayColor(); //同上如果要制造新的抱枕娃娃,这个抱枕娃娃也可以是动物,也可以是人偶,那么就借用动物娃娃的构造函数实例化抱枕娃娃的原型对象,这样就能继承动物娃娃和人偶娃娃的所有属性和方法,这三个之间的关系就成为原型链,当然不只有三个~
之前说到animate.color结果打印出undefined,由于没有传值的,这就需要利用call继承Doll的属性,而后把参数赋值给对应的属性,代码如下:
function Doll(color,height,weight){ this.color = color; this.height = height; this.weight = weight;};Doll.prototype.sayColor=function(){ console.log(this.color);}function AnimateDoll(name,...args){ Doll.call(this, ...args); //新添加一行 this.name=name;}AnimateDoll.prototype = new Doll();AnimateDoll.prototype.sayName=function(){ return this.name;}let animate1=new AnimateDoll('pig','pink',20,10);animate1.name; //piganimate1.sayName(); //piganimate1.color; //pinkanimate1.sayColor(); //pinkanimate1.height; //20animate1.weight; //10继承方法不只有这个原型链,还有其余的,有兴趣,可以自己去看看小红书或者者google~
之前提到过一个问题“为什么非要判断对象类型”,如果没有对象类型,我们就没办法判断一个对象是指向哪个,也就无法实现继承的方法。
到此为止吼~觉得不错,求个赞~觉得有不足的,求个建议~
笔芯
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » JavaScript深入系列之原型与原型链