原型和原型链的主要作用:实现属性和方法的共同继承所以下面的例子都是基于构造函数的。原型函数也是一个对象,是一个属性的集合,所以函数下面也有属性,也可以自定义属性。当我们创建一个函数时,默认会有一个原型属性,它是一个对象(属性的集合)。这个东西就是原型---调用构造函数创建的对象实例的原型对象。原型中还有一个属性构造函数,它指向函数本身。prototypefunctionPerson(){}Person.prototype.name='erdong';varp1=newPerson();varp2=newPerson();console.log(p1.name);//erdongconsole.log(p2.name);//erdong函数Person有一个prototype属性,给这个属性加上name属性。p1和p2是这个函数的实例,访问p1.name和p2.name时,它们的值都是原型下name的值。这个原型对象是p1和p2的实例原型,它下面的所有属性和方法都可以被p1和p2获取和使用。再看原型对象和构造函数的关系:那么实例是如何关联原型对象的呢?__proto__每个JavaScript对象都有的一个属性--__proto__这个属性指向对象的原型。但是,它是一个隐式属性,并不是所有的浏览器都支持它,我们可以把它看作是实例和实例原型之间的桥梁。函数Person(){}varp1=newPerson();console.log(p1.__proto__==Person.prototype);//true上面的p1.__proto__等于原型对象,所以可以看出p1.__proto__指向原型对象。constructor的每个函数都有一个prototype属性,prototype下还有一个constructor属性,指向prototype所在的函数。functionPerson(){}console.log(Person.prototype.constructor==Person)//true以上是原型的几个重要“属性”。下面说一下原型连接。实例和原型JavaScript规定,在读取一个对象的某个属性或方法时,先从自身查找,如果找不到,就去它的__proto__指向的原型对象,如果找不到,就去到原型对象的prototype对象上搜索,如果再找不到,就到原型对象的prototype对象上找...,以此类推,直到找到最上层。至于顶层在哪里,下面会提到。functionPerson(){}varp1=newPerson();console.log(p1.name);//undefinedp1.show();//UncaughtTypeError:p1.showisnotafunction在上面的例子中,p1是aconstructorPerson的实例,在访问p1的name属性和show方法时,没有找到,因为p1是新创建的实例。参见下面的例子:.log(p1.name);//erdongp1.show();//erdongprototype中添加name属性和show方法时,可以正确访问到p1,也就是说当p1在寻找属性(方法),如果自己没有找到,就会去__proto__指向的prototype对象中寻找。我们再看一个例子:p1.name='chen'console.log(p1.name);//chen当我们在p1(对象)上添加一个属性name,此时访问p1.name,输出的是“chen”而不是“erdong”.这是对象查找属性(方法)时的规则。原型的顶层当我们在上面的例子中搜索p1这个名字的时候,发现还没有找到Person.prototype的时候,我们还是应该往下看。下一级是谁?因为Person.prototype是一个对象,所以它有一个__proto__属性,这个属性指向它的原型对象——也就是它对应的构造函数的原型。那么谁是Person.prototype呢?是Object,因为可以通过newObject()创建对象:varobj=newObject();//我们通常写成字面形式:varobj={};其实相当于newObject();只不过javascript是在内部执行的。obj.name='erdong';console.log(obj.name);//二东看图:为什么发现找不到Object.prototype就输出undefined?因为当在Object.prototype中找不到name属性时,它会在Object.prototype指向的原型对象上查找。上面我们提到对象和它的原型对象是通过__proto__关联的,但是javascript规定Object.prototype.__proto__是不存在的,也就是nullconsole.log(Object.prototype.__proto__===null);//是的,这一点必须牢记。原型链原型链也是JavaScript中一个非常重要的概念。之所以叫概念,是因为它不存在,不像对象的属性,也不像对象的方法。我的理解是-(实例)对象的属性或方法的搜索规则。这条规则可以简单也可以复杂。我们总结一下上面的所有知识:每个函数都有一个原型对象(prototype),原型对象包含一个属性(constructor),指向函数本身,函数的实例有一个隐式原型(__proto__),指向构造函数的原型对象(prototype)。查找规则:当我们访问一个实例的某个属性时,首先从实例本身开始查找,如果找不到,就到它里面指向的原型对象,如果再找不到,再查找转到指向其中的原型对象。查找原型对象,找到原型的顶部即可。看图:蓝线代表一条原型链。改变原型functionPerson(){}Person.prototype={name:'erdong',sex:'male',doSoming:function(){console.log(this.name);}}varp1=newPerson();p1.doSoming();console.log(p1.__proto__==Person.prototype);//trueconsole.log(Person.prototype.constructor==Person);//false上面的例子将构造函数的prototype属性重写了起来。虽然p1也能找到名字,但是prototype下的constructor属性不再指向Person。其实就是指向Objectconsole.log(Person.prototype.constructor==Object);//true因为我们重写了Person的原型,此时Person.prototype只是一个普通的对象。即:Person.prototype.constructor=Person.prototype.__proto__.constructor=Object.prototype.constructor=Object当constructor属性很重要的时候,我们可以这样做:functionPerson(){}Person.prototype={constructor:Person,//主动添加构造函数属性name:'erdong',sex:'male',doSoming:function(){console.log(this.name);}}console.log(Person.prototype.constructor==Person);//true当构造函数有很多方法或属性时,可以应用上面的代码示例。继承——原型链是实现继承的一种方式。这里只是简单提一下,后面的文章会详细了解。如果我们没有完全覆盖函数的原型属性,而是让它等于另一个构造函数的实例,会发生什么?functionSuperType(){}SuperType.prototype.name='erdong';SuperType.prototype.getName=function(){returnthis.name;}functionSubType(){}SubType.prototype=newSuperType();varinstance=新的SubType();console.log(instance.name);//erdongconsole.log(instance.getName());//erdong在上面的例子中有两个构造函数SuperType和SubType,SuperType原型有一个name属性和getName方法。Instance是另一个构造函数SubType的实例。本来,instance和SuperType没有任何关系。但是现在实例可以获取name属性和getName方法。原因是SubType重写了原型属性,使其值等于SuperType的实例。因此,存在于SuperType.prototype中的属性和方法现在也存在于SubType.prototype中。看下图:蓝线是SubType.prototype的变化原型(__proto__和prototype)的点(也是实例查找属性的路由),红线是原原型的点(__proto__和原型)。JavaScript高级编程把上面的例子解释为原型链的基本概念——当我们让原型对象等于构造函数的另一个实例时,此时的原型对象会包含指向另一个原型的指针,相应地,另一个原型也包含指向另一个构造函数的指针。再增加一个原型,就是构造函数的另一个实例,那么上面的关系仍然成立,这样一个递进的方式就形成了实例链和原型链。这就是所谓原型链的基本概念。关于原型的方法有PrototypeOf、getPrototypeOf、instanceof、in、hasOwnPropertyisPrototypeOf,用于判断传入对象内部是否有指向原型对象的指针。函数Person(){}varp=newPerson();console.log(Person.prototype.isPrototypeOf(p));//true上面我们提到了实例和原型对象是通过__proto__关联的,__proto__不是Javascript规范,所以我们不能用它来判断关系在现实中实例和原型对象之间。这时候,我们使用isPrototypeOf。getPrototypeOfES6Object是一种新方法,它返回传入对象的原型。functionPerson(){}varp=newPerson();console.log(Object.getPrototypeOf(p)===Person.prototype);//true上面代码输出为true,证明Object.getPrototypeOf(p)得到的是p的原型。instanceof判断前者是否是后者的实例。functionPerson(){}varp=newPerson();console.log(pinstanceofPerson);//trueconsole.log(pinstanceofObject);//true因为p不仅是Person的实例,还是对象,所以也是Object的实例。看下面的例子:console.log(FunctioninstanceofObject);//trueconsole.log(ObjectinstanceofFunction);//trueFunction是Object的实例,而Object是Function的实例。有点绕了弯路,下面会说明情况。in判断前者是否为后者原型链中的属性。functionPerson(){}Person.prototype.name='erdong';varp=newPerson();p.sex='male';console.log('sex'inp);//trueconsole.log('name'inp);//trueconsole.log(p中的'地址');//falsehasOwnProperty检测传入的字符串是否为调用者自身属性,如果是自身属性,则返回true,如果在prototype属性中或不存在,则返回false。functionPerson(){}Person.prototype.name='erdong';varp=newPerson();p.sex='male';console.log(p.hasOwnProperty('name'));//falseconsole.日志(p.hasOwnProperty('性别'));//trueconsole.log(p.hasOwnProperty('地址'));//false和in的区别在于,如果属性存在于包括原型链在内的实例上,则返回true,而hasOwnProperty只有是自己的属性才会返回true。认为我们的(构造函数)函数也是一个对象。上面说了,对象下面会有一个__proto__属性,那么函数的__proto__指向谁呢?console.log(Person.__proto__===Function.prototype);//真正的函数是通过newFunction()创建的,虽然我们通常创建函数不是通过new以下函数:functionsum(num1,num2){returnnum1+num2;}在JavaScript中,应该这样实现:varsum=newFunction("num1","num2","返回num1+num2");所以Person对应的构造函数应该是Function。那么新的问题又来了?Function也是一个函数,也是一个对象,所以它也有__proto__属性和prototype属性。他们指的是什么?console.log(typeof函数);//'函数'console.log(Function.__proto__===Function.prototype);//true看到上面是不是很奇怪?这里解释一下:Function是函数,也是newFunction创建的,所以是自己创建的,它的__proto__指向自己的原型——也就是Function.prototype。那么Function.prototype.__proto__指向谁呢?console.log(Function.prototype.__proto__===Object.prototype)很明显,Function.prototype.__proto__是一个对象,所以指向了Object.prototype。还有一个问题。Object也是一个构造函数,也是一个对象,所以它也应该有prototype和__proto__属性。上面我们提到了Object.prototype。__proto__为空,那么Object.__proto__指向谁呢?console.log(Object.__proto__===Function.prototype);//上面的trueObject.__proto__指向Function.prototype,因为Object是一个函数,所以它的原型是Function.prototype。最后总结一下:看似关系很复杂,其实是一种豁然开朗的感觉。写在最后,文中如有错误,请务必留言指正,万分感谢。喜欢它,让我们一起学习进步。GitHub
