当前位置: 首页 > Web前端 > JavaScript

EverythingisemptyJavaScriptprototype

时间:2023-03-27 01:42:43 JavaScript

ES6带来了太多的语法糖,其中箭头函数隐藏了this的神奇之处,class也隐藏了本文要讨论的原型。最近,我重写了这篇文章。通过本文,您将能够学习到:1.如何使用ES5模拟类;2.理解原型和__proto__;3、了解原型链和原型继承;4.了解更多JavaScript语言。简介:普通对象和函数对象在JavaScript中,一直有万物皆对象的说法。其实在JavaScript中,对象也是有区别的,我们可以把它们分为普通对象和函数对象。Object和Function是JavaScript自带的两个典型的函数对象。函数对象是一个纯函数。所谓函数对象,其实就是一个使用JavaScript的模拟类。那么,什么是普通对象,什么是函数对象呢?请看下面的例子:首先,我们分别创建三个Function和Object的实例:functionfn1(){}constfn2=function(){}constfn3=newFunction('language','console.log(language)')constob1={}constob2=newObject()constob3=newfn1()打印如下结果,可以得到:console.log(typeofObject);//functionconsole.log(typeofFunction);//函数控制台.log(typeofob1);//objectconsole.log(typeofob2);//objectconsole.log(typeofob3);//objectconsole.log(typeoffn1);//functionconsole.log(typeoffn2);//函数控制台.log(typeoffn3);//function上例中ob1、ob2、ob3是普通对象(都是Object的实例),而fn1、fn2、fn3都是Function的实例,称为函数对象。如何区分?其实只要记住这句话:所有的Function实例都是函数对象,其他的都是普通对象。这时,细心的同学就会提出一个问题。开头我们提到了Object和Function都是函数对象,这里说:所有的Function实例都是函数对象。Function也是Function的实例吗?暂时保留这个问题。接下来对本节内容做一个小结:从图中可以看出,对象本身的实现还是依赖于构造函数。原型链是做什么用的?众所周知,作为一门面向对象(ObjectOriented)的语言,它必须具备以下特点:1.对象唯一性2.抽象性3.继承性4.多态性原型链最大的目的就是实现继承性。进阶:prototype和proto原型链是如何实现继承的?首先介绍一下两兄弟:prototype和__proto__,这是JavaScript中普遍存在的两个变量(如果你经常debug的话),然而这两个变量并不是在所有的对象上都存在的,我们先看一张表:objecttypeprototype__proto__commonobject×√functionobject√√首先,我们给出如下结论:只有函数对象才具有原型属性;prototype和__proto__都是在定义函数或对象时由JavaScript自动定义的Created预定义属性。接下来我们验证以上两个结论:functionfn(){}console.log(typeoffn.__proto__);//functionconsole.log(typeoffn.prototype);//objectconstob={}console.log(typeofob.__proto__);//functionconsole.log(typeofob.prototype);//未定义,哇!果然,普通的对象是没有原型的。既然是语言层面的预设属性,那么两者有什么区别呢?我们还是从结论开始,给出以下两个结论:原型是实例的__proto__指向的(被动)__proto__指向构造函数的原型(主动)哇,也就是说下面的代码成立:console.log(fn.__proto__===Function.prototype);//trueconsole.log(ob.__proto__===Object.prototype);//true看起来很酷,瞬间证明了结论,是不是很酷,那么问题来了:既然fn是一个函数对象,那么fn.prototype.__proto__到底等于什么呢?这是我尝试解决这个问题的过程:首先使用typeof获取fn.prototype:"object"的类型;哇,既然是“object”,那fn.prototype不就是Object的一个实例吗?根据以上结论,快速编写验证码:console.log(fn.prototype.__proto__===Object.prototype)//true接下来,如果你想快速编写,在创建函数的时候,JavaScript会不会很快编写函数原型的初始化代码://实际代码functionfn1(){}//JavaScript自动执行fn1.protptype={constructor:fn1,__proto__:Object.prototype}fn1.__proto__=Function.prototype此时,你有开悟的感觉吗?还,因为普通对象是通过函数对象实例化(new)得到的,一个实例不能被再次实例化,不会让另一个对象的__proto__指向它的原型,所以在本节开头提到的结论是普通对象没有原型属性似乎很好理解。从上面的分析我们也可以看出,fn1.protptype是一个普通对象,并没有protptype属性。回顾上一节,我们还有一个疑问:Function也是Function的实例吗?是时候删除shouldletitsetup了。所以此时此刻,请向我展示您的代码!console.log(Function.__proto__===Function.prototype)//true重点:在原型链的部分,我们详细解释了prototype和__proto__。其实这两兄弟的存在主要是为了构建原型链。首先是上一段代码:constPerson=function(name,age){this.name=namethis.age=age}/*1*/Person.prototype.getName=function(){returnthis.name}/*2*/Person.prototype.getAge=function(){returnthis.age}/*3*/constulivz=newPerson('ulivz',24);/*4*/console.log(ulivz)/*5*/console.log(ulivz.getName(),ulivz.getAge())/*6*/解释执行细节:执行1,创建构造函数Person,注意,如前所述,此时Person.prototype已经被自动创建,它包含constructor和__proto__这两个属性;执行2,给对象Person.prototype添加一个方法getName();执行3,给对象Person.prototype添加一个方法getAge();执行4,构造函数Person创建了一个实例ulivz。值得注意的是,当一个构造函数被实例化时,它会自动执行该构造函数。在浏览器中获取5的输出,即ulivz应该是:{name:'ulivz',age:24__proto__:Object//其实就是`Person.prototype`}结合上一节的经验,console.log(ulivz.__proto__==Person.prototype)//true当执行到6时,由于ulivz中找不到getName()和getAge()这两个方法,所以会继续查找prototypechain,而是通过proto向上查找,所以在ulviz.__proto__,也就是Person.prototype中很快就找到了这两个方法,于是停止查找,执行得到结果。这就是JavaScript的原型继承。准确的说,JavaScript的原型继承是通过proto并借助prototype实现的。因此,我们可以得出如下结论:函数对象的proto指向Function.prototype;(回顾)instance.__proto__指向一个函数对象的原型;(复习)普通对象的proto指向Object.prototype;(复习)普通对象没有原型属性;(复习)访问对象的属性/方法时,如果在当前对象上找不到,会尝试访问ob.__proto__,也就是访问对象的构造函数的原型obCtr.prototype,如果还是找不到find到的时候会继续寻找obCtr.prototype.__proto__,这样依次寻找。如果在某个时刻,找到了该属性,则立即返回值并停止搜索原型链,否则返回undefined。为了检验您对以上内容的理解,请分析以下两个问题:1.以下代码的输出是什么?console.log(ulivz.__proto__===Function.prototype)答案:false2.`Person.__proto__`和`Person.prototype.__proto__`指向哪里?分析:如前所述,JavaScript中的一切都是对象。Person显然是Function的一个实例,所以Person.__proto__指向Function.prototype:指向Object.prototypeconsole.log(Person.prototype.__proto__===Object.prototype)//true以验证Person.__proto__所在的原型链中没有Object,也没有Function中Person.prototype.__proto__所在的原型链,结合下面语句验证:/falseUltimate:在原型链图的最后一节中,我们其实还有一个疑问:如果原型链一条一条地搜索,如果找不到,什么时候停止?也就是说,原型链在哪里结束?我们可以用下面的代码快速验证:functionPerson(){}constulivz=newPerson()console.log(ulivz.name)显然,上面的输出是未定义的。下面简述查找过程:ulivz//是一个对象,可以继续ulivz['name']//不存在,继续查找ulivz.__proto__//是一个对象,可以继续ulivz.__proto__['name']//不存在,继续查找ulivz.__proto__.__proto__//是对象,可以继续ulivz.__proto__.__proto__['name']//不存在,继续查找ulivz.__proto__.__proto__.__proto__//空!!!!停止搜索,返回undefined哇,原来路的尽头是空的。最后再回顾一下上一节的demo代码:constPerson=function(name,age){this.name=namethis.age=age}/*1*/Person.prototype.getName=function(){returnthis.name}/*2*/Person.prototype.getAge=function(){returnthis.age}/*3*/constulivz=newPerson('ulivz',24);/*4*/console.log(ulivz)/*5*/console.log(ulivz.getName(),ulivz.getAge())/*6*/我们画一个原型链图,或者说,画它的整个原型链图?请看下图:PS:改写的时候,我把chl(我的中文名字缩写)改成了ulivz(Github名字),所以这张图的chl其实就是ulivz。画这张图的时候,我还在用windows画了这张图,基本上前面的问题都可以回答了。与其说万物皆物,不如说万物皆空似乎更形象。调料:前面已经提到构造函数,但只有原型对象才有构造函数的属性,构造函数用来指向引用它的函数对象。Person.prototype.constructor===Person//trueconsole.log(Person.prototype.constructor.prototype.constructor===Person)//true这是一个循环引用。当然你也可以画在上一节的原型链图中,这里就不赘述了。加链接说明补充:JavaScript中6个内置(函数)对象的原型继承通过前面的讨论,结合相应的代码验证,整理出如下原型链图:可以看出我们加强了这两种观点:任何内置函数对象(类)的proto本身都指向Function的原型对象;除了Oject的原型对象的proto指向null外,其他所有内置函数对象的原型对象的proto都指向object。为了减少读者敲代码的时间,这里给出验证码,希望能促进大家的理解。Array:console.log(arr.__proto__)console.log(arr.__proto__==Array.prototype)//trueconsole.log(Array.prototype.__proto__==Object.prototype)//trueconsole.log(Object.prototype.__proto__==null)//trueRegExp:varreg=newRegExp;console.log(reg.__proto__)console.log(reg.__proto__==RegExp.prototype)//trueconsole.log(RegExp.prototype.__proto__==Object.prototype)//trueDate:vardate=newDate;console.log(date.__proto__)console.log(date.__proto__==Date.prototype)//trueconsole.log(Date.prototype.__proto__==Object.prototype)//trueBoolean:varboo=newBoolean;console.log(boo.__proto__)console.log(boo.__proto__==Boolean.prototype)//trueconsole.log(Boolean.prototype.__proto__==Object.prototype)//trueNumber:varnum=newNumber;console.log(num.__proto__)console.log(num.__proto__==Number.prototype)//trueconsole.log(Number.prototype.__proto__==Object.prototype)//trueString:varstr=newString;console.log(str.__proto__)console.log(str.__proto__==String.prototype)//trueconsole.log(String.prototype.__proto__==Object.prototype)//true总结如果A通过new创建B,然后B.__proto__=A.prototype;__proto__是原型链搜索的起点;执行B.a,如果在B中找不到,到a时,就会在B.__proto__中查找,也就是在A.prototype中查找。如果还是没有A.prototype,就继续往上找。最后,你一定会找到Object.prototype。如果还是找不到,因为Object.prototype.__proto__指向null,所以会返回undefined;为什么一切都是空的,还是那句话,原型链的最顶端一定有Object.prototype.__proto__——>null