< titlesplit >js中的原型和原型链应该是老生常谈的话题了。基本上是前端面试必问的问题,但是很多人还是一头雾水,只知道它的粗浅含义。但是当他问得更深一点(我自己)时,面试官支支吾吾。为了下次面试能“装b”,熬夜了,特此记录一下加深印象,希望对有需要的朋友有所帮助。先来看一张图↓相信第一眼看到这张图的人会觉得很繁琐,不想看。其实,如果你耐心看完这篇文章,你的感受也不过如此。这种图真的好用,好用,好用。重要的事情说三遍,开始吧。构造函数创建对象我们首先使用构造函数创建对象functionPerson(){}varperson=newPerson();person.name='Kevin';console.log(person.name)//Kevin就是一个例子使用Person构造函数创建一个实例对象person,这个就不用多说了。prototype每个函数都有一个prototype属性,不信可以随便找一个函数打开F12看看。(注:prototype是函数特有的属性)functionPerson(){}//虽然在注释里写了,大家要注意://prototype是函数才有的属性Person.prototype.name='Kevin';varperson1=newPerson();varperson2=newPerson();console.log(person1.name)//Kevinconsole.log(person2.name)//Kevin,这个函数的原型属性指向什么?是是这个函数的原型吗?实际上,该函数的prototype属性指向一个对象,该对象是调用构造函数创建的实例的原型,即本例中person1和person2的原型。那么什么是原型呢?你可以这样理解:每个JavaScript对象(null除外)在创建时都会与另一个对象相关联。这个对象就是我们所说的原型,每个对象都会“继承”原型的属性。让我们用一张图来表示构造函数和实例原型之间的关系:在这个图中我们使用Person.prototype来表示实例原型。那么我们如何表达实例与实例原型的关系,即person与Person.prototype的关系呢?这时候我们就要说到第二个属性:__proto__,为了证明每一个JavaScript对象(null除外)创建的时候,都会有另外一个对象与之关联。这个对象的真实性就是我们所说的原型。我们可以用一个例子来证明。函数Person(){}varperson=newPerson();console.log(person.__proto__===Person.prototype);//true在这种情况下,我们来更新关系图:既然实例对象和构造函数都可以指向原型,那么原型中是否有一个属性可以指向实例或构造函数?首先,我们可以排除原型没有指向实例的属性,因为一个构造函数可以new多个实例。构造函数主要讲原型中的构造函数,每个原型都有一个constructor属性指向构造函数。老规矩,你可以在浏览器中验证一下。函数Person(){}console.log(Person===Person.prototype.constructor);//true得到如下关系图:从上面的关系图和例子,我们梳理总结一下:functionPerson(){}varperson=newPerson();console.log(person.__proto__==Person.prototype)//trueconsole.log(Person.prototype.constructor==Person)//true//顺便学一个ES5的方法,可以得到对象的原型console.log(Object.getPrototypeOf(person)===Person.prototype)//true了解了构造函数、实例和实例原型之间的关系之后,我们来谈谈实例和原型之间的关系呢?实例和原型当你读取上面实例的属性却找不到它们时,会发生什么?如果找不到它们,您将在与实例关联的原型的属性中搜索它们。如果你还是找不到它们到达时,你会去原型的原型中搜索,直到找到最顶层。functionPerson(){}Person.prototype.name='Kevin';varperson=newPerson();person.name='Daisy';console.log(person.name)//Daisydeleteperson.name;console.log(person.name)//Kevin在这个例子中,我们给实例对象person添加了一个name属性。当我们打印person.name时,结果自然是Daisy。但是当我们删除person的name属性,读取person.name,如果person对象中没有找到name属性,就会从person的原型中查找,也就是person.__proto__,也就是Person.prototype。幸运的是,我们找到了name属性,原来是Kevin。但是,如果您还没有找到它怎么办?原型的原型是什么?其实原型对象是通过Object的构造函数生成的。结合我们前面说的,实例的__proto__指向的是构造函数的原型,那么我们更新一下关系图:原型链中Object.prototype的原型呢?null,我们可以打印:console.log(Object.prototype.__proto__===null)//true所以Object.prototype.__proto__的值为null,Object.prototype没有原型,其实表达了一个意思。所以在搜索属性的时候,如果找到了Object.prototype,就可以停止搜索了。分析到此结束。蓝线其实就是原型链。总结一下,这里有几点需要注意:constructor首先是constructor属性,我们来看一个例子:functionPerson(){}varperson=newPerson();console.log(person.constructor===Person);//true获取person.constructor时,person中其实没有constructor属性。当constructor属性无法读取时,会从person的原型中读取,也就是Person.prototype,这个prototype恰好存在于prototype中,so:person.prototype。constructor===Person.prototype.constructor_proto_后面跟着__proto__,大部分浏览器都支持这种非标准的方法来访问原型,但是在Person.prototype中是不存在的,实际上它来自于Object。原型与其说是一个属性,不如说是一个getter/setter。使用obj.__proto__时,可以理解为返回Object.getPrototypeOf(obj)。
