前言在前端领域,原型和原型链是每个前端er都必须掌握的概念。我们在采访或一些技术博客中多次看到这个概念。由此可见这东西对于前端来说是多么的重要。其实本身并不难理解,但是很多刚进入前端行业的同学,看到prototype和__proto__还是会觉得难以理解,然后脑子里乱成一团,就像我一样。不过这是很正常的事情,没什么大不了的,就像我们要学跑步,就要先学会走路一样。任何事情都有一个过程。所以现在跟我来克服这个困难。通过本文你将掌握以下知识点:理解__proto_;了解原型;理解javascript中对象的概念;了解原型和原型链;理解javascript中类的概念;了解新的实施;理解instanceof的实现;了解javascript继承;加深对javascript语言的理解。这也是本文的写作思路。对象那么让我们从对象的概念开始。其实相信大家对对象这个概念都不陌生。有句话说“javascript中的一切都是对象”,其实这个说法是错误的,举个很简单的例子,javascript中简单的基本类型(string、boolean、number、null、undefined、symbol)本身并不是对象。其实javasript中的对象主要分为函数对象和普通对象。其中:StringNumberBooleanObjectFunctionArrayDateRegExpError这些是函数对象,也叫内置对象。函数对象本身其实就是一个纯函数,javascript用它们来模拟类。普通对象很简单,就是我们常见的对象:constobj={name:'juefei',desc:'cool'}可能说到这里,你还是搞不懂什么是函数对象,什么是普通对象,那么我们来看下面的代码:constobj1={};constobj2=newObject();functionfunc1(){}constobj3=newfunc1();constfunc2=newfunction(){}constfunc3=newFunction()然后我们分别打印出来:console.log(obj1);//objectconsole.log(obj2);//objectconsole.log(obj3);//objectconsole.log(func1);//functionconsole.log(func2);//functionconsole.log(func3);//function所以可以看出obj1、obj2、obj3是普通对象,都是Object的实例,而func1、func2、func3都是Function的实例,称为函数对象。我们再来看看:console.log(typeofObject);//functionconsole.log(typeofFunction);//function是不是惊呆了,原来Object和Function都是Function的实例。所以我们得出一个结论:只要是Function的实例就是函数对象,其余都是普通对象。同样,我们也可以看出不仅Object是一个函数对象,而且Function本身也是一个函数对象,因为我们通过console.log(typeofFunction)知道Function是Function的一个实例;是不是又有点迷糊了?没关系,此时你只需要记住我们刚刚为完成目标所做的结论:只要是Function的实例就是函数对象,其余都是普通对象。那么说到对象,我们从上面可以看出,一个对象是通过构造函数new创建的,这其实和原型、原型链有很大的关系。那么原型和原型链是用来做什么的呢?Prototypes说到这两个概念,首先要介绍两个东西:__proto__和prototype。这两个变量在javascript的语言中随处可见。我们不关心他们。让我们先看看它们。A表:所以,请先记住以下结论:只有函数对象才有原型属性,普通对象没有这个属性。函数对象和普通对象都具有属性__proto__。prototype和__proto__是在创建函数或对象时自动生成的属性。然后我们来验证一下:functionfunc(){//func调用了构造函数}console.log(typeofunc.prototype);//objectconsole.log(typeoffunc.__proto__);//functionconstobj={}console.log(typeofobj.__proto__)//objectconsole.log(typeofobj.prototype)//undefined(看,普通对象真的没有prototype属性),所以验证了刚才的结论:只有函数对象才有prototype属性,普通对象没有这个属性函数对象和普通对象都具有属性__proto__。prototype和__proto__是在创建函数或对象时自动生成的属性。你看,我又重复了一遍。我不是为了凑字数,而是为了加深大家的记忆,这对我们下一章来说很重要。然后我们看一下下面的代码:console.log(obj.__proto__===Object.prototype);//trueconsole.log(func.__proto__===Function.prototype);//true所以我们得到如下结论:实例的__proto__属性主动指向构造的原型;prototype属性由__proto__属性指向。这就是prototype属性和__proto__属性的区别和联系。这可能绕了点弯路,让我们多看几遍这一节,背诵一下我们的结论。让我们继续。那么问题来了,既然func是一个函数对象,而函数对象有prototype属性,那么func.prototype.__proto__等于什么呢?解决这个问题,我们想一想:首先,我们看看func.prototype是什么:console.log(typeoffunc.prototype);//object嗯,我们知道,func.prototype是一个对象,既然是对象,func.prototype不是Object的实例吗?也就是说,func的.prototype.__proto__属性必须指向Object.prototype!好吧,我们来验证一下:console.log(func.prototype.__proto__===Object.prototype);//true看看是不是这样的。看到这里,我们也应该知道javascript是如何在我们创建构造函数的时候自动为我们生成__proto__和prototype属性的了。哈哈,没错。//我们手动创建func函数functionfunc(){}//javascript悄悄执行如下代码:func._proto=Function.prototype;//实例的__proto__属性主动指向构造好的prototypefunc。prototype={constructor:func,__proto:Object.prototype//上面我们只是验证了一下,不要忘了}我也给大家画了一张图(够贴心够老套):所以prototype又叫Explicitprototypeobject,__proto__也称为隐式原型对象。嗨,看到这里,是不是有一种脑洞大开的感觉呢。哈哈,至此你应该已经明白原型的概念了,如果还不明白,再看一遍上面的章节。最好拿出纸笔画个图,顺便拿出电脑敲一下示例代码。好了,我们梳理一下思路,再来看看什么是原型链。在原型链引入这个概念之前,我们先看下面这段代码:functionPerson=function(name,desc){this.name=name;this.desc=desc;}//***1****//Person.prototype.getName=function(){returnthis.name;}//***2****//Person.prototype.getDesc=function(){returnthis.desc;}//***3****//constobj=newPerson('juefei','cool');//***4****//console.log(obj);//***5****//console.log(obj.getName);//***6****//接下来我们一步一步来分析:1.创建一个构造函数Person,这时候会自动创建Person.portotype,里面包含constructor和__proto__两个属性;2.为对象Person.prototype增加一个新方法getName;3.为对象Person.prototype添加一个新方法getDesc;4、构造函数Person创建一个新的实例:obj(创建实例时会自动执行构造函数);5.打印实例obj:{name:'juefei',desc:'cool'}根据上一节的结论,我们得到:obj.__proto__=Person.prototype;6、执行到第六步,由于在实例obj上找不到getName()方法,它会自动继续通过自己的__proto__向上查找,找到Person.prototype,然后发现恰好是上面的Person.prototype有一个getName()方法,所以当它找到这个方法的时候,就停止寻找了。怎么样,有没有环环相扣的感觉?它们形成一条链,没错,这就是原型链。我们得出如下结论:当访问一个对象的属性/方法时(假设对象叫obj),如果在当前对象上找不到,就会尝试通过obj.__proto__,obj.__proto__来寻找指向其构造函数的原型(假设叫objCreated),于是自动去objCreated.prototype的属性/方法中寻找,结果还是没有找到,再访问objCreated.prototype.__proto__,继续搜索直到找到,然后停止搜索原型链对,如果仍然找不到,返回undefined。沿着原型链继续查找,直到找到Object.prototype.__proto__,它指向null,所以返回undefined。是不是理所当然?我再给大家画了一张图(请参考上面的👆图):接下来,我们补充一些概念:任何内置函数对象本身的__proto__属性都指向Function的原型对象,即:Function.prototype;除了Object.prototype.__proto__指向null外,所有内置函数对象(built-infunctionobject.prototype.__proto__)的原型对象的__proto__属性都指向Object。我们得到如下图的终极原型链:这张图,我最后给出我们经常看到的原型链的图片:比较好,拿出一张纸和一支笔画一张图,按照文中的解释以上章节,相信大家可以轻松看懂。javascript中的类刚才我们终于明白了什么是原型和原型链。下面我们就根据以上概念来解释一下javascript中的类。我们知道,在面向对象的语言中,一个类是可以被多次实例化的。这种实例化意味着我们可以根据构造函数独立复制多个独立的实例,这些实例是独立的。但是在javascript中实际上不是这样,因为它没有这种复制机制。我们不能创建一个类的多个实例,我们只能创建这个类的多个对象,因为它们都是通过原型和原型链关联到同一个对象。所以在javascript中,类是通过原型和原型链来实现的,其实就是一种委托的方式。new的实现理解了上面javascript中类的概念,那么我们应该很容易理解new的过程,其核心无非是执行原型链的链接:functionmyNew(Cons,...args){letobj={};obj.__proto__=Cons.prototype;//执行原型链接letres=Cons.call(obj,args);returntypeofres==='object'?res:obj;}instanceof的实现然后学习原型和原型链,instanceof的实现一定很简单,也是通过原型和原型链实现的:functionmyInstanceof(left,right){letrightProto=right.prototype;letleftValue=left.__proto__;while(true){if(leftValue===null){returnfalse;}if(leftValue===rightProto){returntrue;}leftValue=leftValue.__proto__;}}这个过程我就不解释了,因为我知道你能看懂,哈哈。我们都知道javascript继承也是通过原型和原型链来实现的,所以我在这里介绍两种常见的继承方式:1.组合继承://combinedinheritance//通过调用继承Parent的属性,并传递入参//将Child的原型对象指向Parent的实例,从而继承Parent的功能functionParent(value){this.val=value;}Parent.prototype.getValue=function(){console.log(this.val);}functionChild(value){Parent.call(this,value);//继承Parentd的属性}Child.prototype=newParent();2.Parasitic组合继承//Parasitic组合继承//通过调用Attributes继承Parent,并传入参数//通过Object.create()functionParent(value){this.val=value;}Parent.prototype继承Parent函数。getValue=function(){console.log(this.val);}functionChild(value){//继承Parentd的属性Parent.call(this,value);}Child.prototype=Object.create(Parent.prototype,{constructor:{value:Child,writable:true,configurable:true,enumerable:false}})总结如果A通过new创建B,则B.__proto__=A.prototype;执行B.a,如果在B中没有找到a,就会在B.__proto__,也就是在A.prototype中查找,如果仍然没有A.prototype,则继续向上查找,最终会在Object.prototype中查找被找到,如果没有找到,因为Object.prototype.__proto__指向null,所以会返回undefined;原型链的顶端,一定有Object.prototype.__proto__——>null。可见原型和原型链是多么的强大,希望看完这篇文章的你不再害怕。写完这篇文章已经快凌晨两点了,如果你觉得这篇文章对你有一点收获,请点赞支持!!作者:爵飞链接:https://juejin.im/post/5dcc3840e51d4510912421fd来源:掘金作者版权所有。商业转载请联系作者授权,非商业转载请注明出处。
