前言在前端开发过程中,涉及到JS原理的内容就是常用的模块,这些模块不仅常用而且非常重要,但是说到原理会有点难理解,尤其是对于接触JS时间不长的开发者来说。这篇博文就是分享关于JS原型和原型链的知识点。虽然复杂难懂,但很重要,值得收藏,以备后用。1、原型背景在JS中,除了基本数据类型(也叫简单数据类型)之外的类型都是引用数据类型(也叫复杂数据类型),即对象;也就是说,在JS的宇宙中,一切都是对象。在ES6之前,由于JS中没有类的概念(ES6中引入了类,但它只是语法糖),在实际开发中关联所有对象就成了一个问题,于是原型和原型链的概念应运而生。Prototype字面解释(来自网络)原型(prototype)一词来自拉丁语proto,意为“初始”,意为形式或模型。在非技术文本中,原型是给定种类的代表性示例。在基于原型的编程中,原型是原始对象;新对象是通过复制原型创建的。JS中原型(Prototype)的概念关于JS中原型的概念,网上的文章太多了,解释也大同小异,所以这里根据网上有用的解释和自己总结一下,重新总结一下JS中原型的概念,由浅入深定义如下:JS中的原型(Prototype)是基于继承的。其实原型就是一个对象,它是JS中继承的基础。JS中的继承是基于原型的继承。作用是实现对象继承。JS中的每个对象在创建时都会与另一个对象相关联。这个关联对象就是原型,每个对象都会继承原型。需要注意的是,对象是特殊的,它没有对应的原型。原型(Prototype)的其他定义Prototype可以理解为一个JS方法的属性。每创建一个函数方法,JS都会给函数方法添加一个名为prototype的属性。这个原型是函数方法Object的原型,它默认有一个constructor属性指向原方法的对象,任何添加到原型上的属性和方法都在这个构造函数中,所有同类型的实例都会共享这个原型对象,实例对象的__proto__属性指向这个对象,method的prototype属性指向这个对象。其实每个函数都会有一个prototype属性与之对应。原型是构造函数创建的对象的原型。它实际上是指向原型对象的指针。原型对象包含可由所有实例共享的属性和方法。构造函数中的原型是显式原型,原型对象有一个指向函数本身的构造函数属性。原型对象(prototype)上面解释了原型的内容后,为什么要在这里写原型对象呢?就是因为网上的写法太多了,很容易让不太清楚的开发者感到迷茫,所以单独提一下。具体如下:functionFunction(){}console.log(Function.prototype)输出结果如下:在JS中,每个函数都有一个原型属性,它指向函数的原型(也叫显式原型),这个原型是一个对象,所以原型也叫原型对象,每个JS对象都继承一个原型的属性和方法。每个实例对象(object)都有一个私有属性(即__proto__)指向其构造函数的原型对象。原型对象也有自己的原型对象,逐层往上,直到某个对象的原型对象为null。根据定义过程,null是没有原型的,所以这是原型链的最后一步,下面会介绍。__proto__属性(注意proto左右各有2个下划线)JS中所有对象(null除外)都有一个__proto__属性(__proto__不是标准属性,只适用于部分浏览器,标准方式是要使用Object.getPrototypeOf()),__proto__指向实例对象的构造函数的原型(即原型对象),__proto__一般称为隐式原型,它还包含一个constructor属性,指向创建实例Function的构造函数,具体示例如下:prototype)//输出结果For:true从上面的例子可以看出,stu是一个实例对象,Student是student的构造函数,student的__proto__属性指向构造函数Person的原型。最后,其实大部分浏览器都支持__proto__这种非标准的方法来访问原型,但它并不存在于Student.prototype中,而是实际上来自于Object.prototype,相当于一个getter/setter。使用object.proto时,就是返回的Object.getPrototypeOf(object)。注意:如果找不到调用实例对象的方法,会去原型对象继续查找。hasOwnProperty方法在访问对象的属性时,该属性可能来自于对象本身,也可能来自于对象的属性所指向的原型。当不确定对象的来源时,使用hasOwnProperty()方法。确定属性是否来自对象本身。注意:hasOwnProperty()方法可以用来判断对象本身是否添加了对象,但是无法判断原型中是否存在,因为很有可能属性不存在,也就是它无论属性是否存在于原型中,都将返回false。in关键字(运算符)in关键字或运算符用于判断对象中是否存在某个属性,但是在查找该属性时,会先在对象本身中查找,如果在对象本身中找不到的话,您将在原型中寻找它。也就是说,只要该属性存在于对象和原型的一处,就返回true。扩展:在判断属性是否存在于原型中时,如果属性存在但对象本身不存在,则该属性一定存在于原型中!原始对象的原型会在JS中所有原始引用类型中的构造函数原型上定义方法,由Object构造函数生成,以最原始的方式创建。实例的原型指向构造函数的原型。原型和实例读取实例属性时,如果找不到该属性,则会在与该实例关联的原型中查找该属性,如果找不到,则会查找原始对象的原型,以此类推,直到找到顶层。原型的作用1、使用继承的方法解决方法重载问题;2.扩展类的功能,添加内置方法和属性。原型中的this指向原型中的实例化对象。原型访问方式关于原型访问方式:如果想访问一个对象的原型,可以通过ES5中的Object.getPrototypeOf()方法和ES6中的__proto__属性来访问。原型对象的使用场景在实际开发中,开发者可能会使用JS类库,但是当发现当前类库中不存在想要的属性或方法时,又无法修改源码,又不想给每个实例对象时单独定义相关的属性和方法,可以考虑使用原型对象进行扩展使用。原型使用示例示例1:在数组原型中添加移除数组中值的方法,使用时只需要调用函数填入值即可。具体如下:letfruits=["apple","banana","cherry","orange","melone"];Array.prototype.remove=function(v){this.splice(v,1);//根据输入的下标截取一个对应的元素returnthis;}fruits.remove(2);//输入数组的下标,这里是要移除的数组的第三个元素console.log("----fruits:",fruits);//输出结果为:'apple','banana','orange','melone'例2:使用原型对象functionStudent()扩展自定义对象{}//定义Student构造函数varstu=new学生();//实例化对象stu.name="zhoujielun";stu.age=28;/*此时发现缺少手机号属性,使用原型对象展开**/Student.prototype。phone="185****1111";console.log(stu.phone)//输出结果为:185****1111例三:关于原型函数的试题Aaa(){}functionBbb(value){this.value=value;}functionCcc(value){if(value){this.value=value;}}Aaa.prototype.value=1;Bbb.prototype.value=1;Ccc。prototype.value=1;console.log(newAaa().value);//输出结果为:1console.log(newBbb().value);//输出结果为:undefinedconsole.log(newCcc(2).value);//输出结果为:2上面输出结果分析,newAaa()是构造函数创建的对象,它本身没有value属性,所以我会去寻找它的原型,发现原型的value属性的属性值为1,所以输出值为1;newBbb()是一个构造函数创建的对象,它有一个参数值,但是该对象不传递参数,所以输出值是undefined;newccc()是构造函数创建的对象,构造函数有一个参数值,传入的参数值为2,当执行函数检测到if条件为真时,执行语句this.value=2,所以输出值为2。构造函数(constructor)的定义实际上是一个使用new关键字调用(实例化对象)的函数,称为构造函数。每个原型都会对应一个构造函数属性,指向其关联的构造函数。构造函数是一个“真正的”函数。定义的时候需要大写。任何函数都可以用作构造函数。构造函数和普通函数之间的区别在于功能级别。构造函数的主要作用是初始化对象,配合new关键字使用。使用new是从头开始创建一个新的对象。构造函数就是给初始化的对象添加属性和方法。扩展:new关键字,在申请内存创建对象时调用new时,程序后台会隐式执行newObject()创建对象,所以new关键字创建的字符串和数字不是引用类型,而是非-值类型。一个构造函数的例子,如下://Thisisaconstructor.functionStudent(name){this.name=name;}varcon=newStudent("李明");//constructorif(con.constructor==String){console.log("112233");}constructor其他方面1.一个构造器会生成一个实例的原型(prototype)属性,通过它可以指向对应的实例原型(即原型对象);2、原型对象有一个constructor属性,通过它可以指向对应的构造函数;3、构造函数和原型对象可以通过属性相互指向;4.对象new指的是new的构造函数。new创建之后,会生成一个实例对象(这里的实例对象不是原型对象),实例对象会继承原型对象的方法。原型(prototype)、构造函数(constructor)、实例对象(即__proto__)构造函数与实例对象的关系:在__proto__中的每一个实例对象中,都会同时存在一个constructor属性,这个constructor属性指向构造函数创建了实例。实例对象与构造函数的关系:每个实例对象中的__proto__指向构造函数中的原型,两者相等。另一种:原型适合封装方法,构造函数适合封装属性。将两者结合形成复合模式。另外二:将所有的属性和方法封装在同一个构造函数中称为动态原型模式,只在需要的时候在构造函数中初始化原型,综合了构造函数和原型的优点。函数对象概要原型:JS中所有函数的prototype属性都是显式原型。__proto__:JS中任何对象的__proto__属性都是隐式原型。constructor:JS中所有原型和实例对象都具有的constructor属性。即在声明一个函数方法时,会在方法上加上一个prototype属性,指向默认的原型对象,而prototype的constructor属性也指向方法对象,这两个属性会在d对象创建时被创建的属性引用。2、访问原型链中对象的过程在JS中访问对象时,首先要检查对象本身是否有你要使用的属性或方法,如果有则直接使用;你想使用的属性或方法,但你不会搜索它自己的原型;逐级查找,有则使用,继续向上查找,直到找到,使用递归访问最后的末尾,如果找不到末尾,则值为null。原型链的概念原型链实际上是一种原型的搜索机制,即寻址链。原型上的属性和方法的查找是按照一定的顺序沿着原型链进行的。JS中的每个对象都对应一个proto属性,指向隐式原型的指针形成一个线性链,即原型链。在JS中,每个函数都有一个原型对象,所有函数的默认原型都是一个Object实例,每个继承父类函数的子函数的对象都包含一个内部属性proto,其中包含一个指向父类函数的指针prototype,如果父类函数的prototype对象的proto属性是更高层的祖父类函数,这个顺序过程就是原型链。上图中,由相互关联的原型组成的链结构就是原型链,也就是红线的过程。什么是原型指针?原型指针是连接原型对象的地址桥,是一种中间连接。原型对象实际上包含两部分:原型数据和原型指针。原型数据用于存储方法和属性,原型指针用于检查和验证循环链表以进行搜索操作。如果原型对象等于另一种类型的实例对象,则原型对象将包含指向另一个原型对象的指针,对应的其他原型对象也包含指向另一个构造函数的指针;如果另一个原型对象是Another类型的实例对象,那么上面描述的关系仍然成立;这样就形成了实例对象和原型对象的链,这就是原型链的概念。原型链规则new关键字构造的实例对象后,其原型指针指向本类的原型对象,原型对象指针默认会指向Object原型对象。原型链特点1、原型链的作用是实现继承;2、包含引用类型值的原型属性或方法将被所有实例共享;参数在类型的构造函数中传递;4.读取对象的属性时,会自动在原型链中查找对应的属性;5、设置对象的属性值时,不会查找原型链。如果没有该属性,直接添加该属性并设置相应的值;6.方法定义在原型中,构造函数定义属性给对象本身。原型链的作用是在访问实例对象的属性和方法时。如果实例对象中不存在该属性或方法,则会在实例对象的原型上查找。如果还是没有找到,就会在prototype的prototype中。向上查找,依此类推,直到找到原型的末尾。通过原型链,实例对象可以获得其原型上的属性或方法。原型链示例示例1:新建一个数组,数组方法继承自数组的原型//数组的原型包含了数组需要使用的各种方法。如果你想使用它,你必须继承它。变量数组=[];//创建一个新数组arr.map===Array.prototype.map;//继承数组原型上的map方法,即继承自arr.__proto__,而arr.__proto__即Array.prototype。示例2:输出如下结果Fun.prototype.x=1;varfun1=newFun()Fun.prototype={x:2,y:3}varfun2=newFun();console.log(fun1.x,fun1.y)//输出结果:1undefinedconsole.log(fun2.x,fun2.y)//输出结果:23示例3:输出如下结果varFun=functionFun(){}Object.prototype.x=function(){console.log('x()')}Function.prototype.y=function(){console.log('y()')}varfun=newFun()fun.x()fun.y()Fun.x()fun.y()outputis:x()undefinedx()y()原型链的末尾原型链的末尾为null,Object中的原型.prototype为null,即Object.prototype是原型链的末尾。因此,Object.prototype.__proto__的值为null,Object.prototype没有原型。其实表达的是同一个意思,就是当你在查找属性的时候找到了Object.prototype,就可以停止查找了。扩展:实现类的继承JS(ES6之前)实现类的继承分为两步:继承构造函数中的属性和方法(构造函数继承);继承对象原型中的属性和方法(原型链继承)。ES6之后的继承,可以使用class关键字结合extends关键字来实现继承。ES6引入了class关键字来声明一个类,一个类(class)可以通过extends继承父类的属性和方法。语法是“classsubclassnameextendsparentclassname{...};”。最后,通过这篇关于JS中的原型和原型链的文章的介绍,如果你仔细阅读并实践例子,你应该对这些知识点有很好的掌握。空间里的内容虽然很多,但是分开看也不会那么复杂。是一篇值得一读的文章,特别适合对原型和原型链不是很清楚的开发者。无论是在开发过程中,还是在求职面试中,这个知识点都是必不可少的。准备好了,重要性就不赘述了。欢迎关注,交流,共同进步。本文参加了“SegmentFault思维写作挑战赛”,正在阅读的朋友欢迎加入。
