当前位置: 首页 > 科技观察

深入理解JavaScript原型概念

时间:2023-03-17 19:58:32 科技观察

Prototype在JavaScript中是一个比较难的概念,和原型相关的属性有很多。对象具有“[[prototype]]”属性,函数对象具有“原型”属性,原型对象具有“构造函数”属性。为了弄清楚原型以及与原型相关的这些属性之间的关系,才有了这篇文章。相信通过本文,您一定能够清楚地了解原型。让我们现在开始原型之旅。认识原型在开始介绍原型之前,我们先来了解一下什么是原型?在JavaScript中,原型也是一个对象,通过它可以实现对象的属性继承,而JavaScript对象包含一个“[[Prototype]]”内部属性??,它对应于对象的原型。“[[Prototype]]”是对象的内部属性,不能直接访问。所以为了方便查看一个对象的原型,Firefox和Chrome提供了非标准的(并非所有浏览器都支持)访问器“__proto__”(ECMA引入了标准的对象原型访问器“Object.getPrototype(object)”)。例子分析下面举个例子看看prototype的概念:functionPerson(name,age){this.name=name;this.age=age;this.getInfo=function(){console.log(this.name+"is"+this.age+"yearsold");};}varwill=newPerson("Will",28);在上面的代码中,通过Person构造函数创建了一个will对象。让我们逐步使用will对象来理解原型。第一步:查看will对象的原型通过如下代码,可以查看will对象的原型:console.log(will.__proto__);console.log(will.constructor);结果分析:“Person{}”对象就是将Prototype的对象,展开Chrome可以看到,“Person{}”是一个原型对象,它还有一个“__proto__”属性(对应原型的prototype)。在此代码中,还使用了“constructor”属性。在JavaScript的原型对象中,还包含一个“constructor”属性,对应于创建所有指向原型的实例的构造函数。通过constructor这个属性,我们可以判断一个对象是否是数组类型functionisArray(myArray){returnmyArray.constructor.toString().indexOf("Array")>-1;}这里,遗嘱对象本身并没有“constructor”属性,而是通过原型链查找找到了遗嘱原型(will.__proto__)的“constructor”属性,得到了Person函数。第二步:检查对象will的原型(will.__proto__)由于will的原型“Person{}”也是一个对象,那么我们也可以检查“will的原型(will.__proto__)”的原型。运行以下代码:console.log(will.__proto__===Person.prototype);console.log(Person.prototype.__proto__);console.log(Person.prototype.constructor);console.log(Person.prototype.构造函数===人);结果分析:先看“will.__proto__===Person.prototype”。在JavaScript中,每个函数都有一个原型属性。当一个函数作为构造函数创建一个实例时,该函数的prototype属性值会作为原型赋值给所有的对象实例(即设置实例的__proto__属性),即prototype所有实例都是指函数的原型属性。理解了构造函数的prototype属性之后,就必须理解为什么第一句的结果为真。prototype属性是函数对象所独有的。如果不是函数对象,就没有这个属性。当通过“Person.prototype.__proto__”语句获取遗嘱对象原型时,会获取到“Object{}”对象。后面你会看到所有对象的原型都会追溯到“Object{}”这个对象。对于原型对象“Person.prototype”的“构造函数”,根据前面的介绍,它会对应Person函数本身。从上面可以看出,“Person.prototype”对象和Person函数对象通过“constructor”和“prototype”属性实现了相互引用(下图将展示这种相互引用关系)。第三步:查看对象Object的原型从前面的部分可以看出,will的prototype的原型是“Object{}”对象。实际上在JavaScript中,所有对象的原型都会追溯到“Object{}”对象。我们通过一段代码来看看“Object{}”对象:console.log(Person.prototype.__proto__===Object.prototype);console.log(typeofObject);console.log(Object);console.log(Object.prototype);console.log(Object.prototype.__proto__);console.log(Object.prototype.constructor);通过下面的代码可以看出:Object对象本身就是一个函数对象。既然是Object函数,就一定要有prototype属性,所以可以看到“Object.prototype”的值就是原型对象“Object{}”。反之,当访问“Object.prototype”对象的“constructor”属性时,得到的是Object函数。另外,当通过“Object.prototype.__proto__”获取Object原型的原型时,会得到“null”,也就是说“Object{}”原型对象是原型链的末端。第四步:查看对象Function的原型在上面的例子中,Person是一个构造函数,函数在JavaScript中也是对象,所以我们也可以通过“__proto__”属性找到Person函数对象的原型。console.log(Person.__proto__===Function.prototype);console.log(Person.constructor===Function)console.log(typeofFunction);console.log(Function);console.log(Function.prototype);console.log(Function.prototype.__proto__);console.log(Function.prototype.constructor);结果分析:在JavaScript中,有一个Function对象(类似于Object),它本身就是一个函数;所有函数(包括Function、Object)的原型(__proto__)都是“Function.prototype”。作为一个函数,Function对象会有一个prototype属性,它会对应“function(){}”对象。Function对象作为一个对象,有一个“__proto__”属性,对应“Function.prototype”,即“Function.__proto__===Function.prototype”对于Function的原型对象“Function.prototype”,prototype对象的“__proto__”属性会对应“Object{}”对比prototype和__proto__对于“prototype”和“__proto__”这两个属性,有时候可能会混淆,“Person.prototype”和“Person.__proto__”是完全不同的。这里简单介绍一下“prototype”和“__proto__”:对于所有的对象,都有一个__proto__属性,对应于对象的原型对于函数对象,除了__proto__属性,还有一个prototype属性,当一个函数作为构造函数创建一个实例时,该函数的prototype属性的值会作为原型赋值给所有的对象实例(即设置实例的__proto__属性)你一定学过一个很多关于原型。但是现在肯定觉得上面例子中的关系很乱,一会儿是原型,一会儿是原型的原型,还有Function、Object、constructor、prototype等等的关系。现在举例说明上面例子中分析得到的结果/关系,相信这张图能让你豁然开朗。上图总结如下:所有的对象都有一个“__proto__”属性,对应对象的原型。所有的函数对象都有一个“prototype”属性,这个属性的值会赋给函数创建的对象。“__proto__”属性所有的原型对象都有一个“constructor”属性,对应于创建所有的构造函数对象和指向原型实例的原型对象。“原型”和“构造函数”属性相互关联。原型改进示例在上面的示例中,“getInfo”方法是构造函数Person的成员。当通过Person构造两个实例时,每个实例将包含一个“getInfo”方法。varwill=newPerson("Will",28);varwilber=newPerson("Wilber",27);前面我们了解到,原型是为了方便属性的继承,所以可以将“getInfo”方法作为Person(Person.__proto__)的原型,这样所有的实例都可以通过原型继承来使用“getInfo”方法。所以修改示例如下:age+"yearsold");};修改后的结果为:原型链,因为每个对象和原型都有一个原型,对象的原型指向对象的父对象,父对象的原型指向父对象的父对象。这种原型是逐层连接起来的,形成原型链。在《UnderstandingJavaScript’sScopeChain》一文中,介绍了通过作用域链和原型链来查找标识符和属性。下面我们继续看基于原型链的属性查找。属性查找当查找对象的属性时,JavaScript遍历原型链,直到找到具有给定名称的属性,直到查找到达原型链的顶部(即“Object.prototype”),如果仍未找到指定的属性,将返回undefined。看一个例子:functionPerson(name,age){this.name=name;this.age=age;}Person.prototype.MaxNumber=9999;Person.__proto__.MinNumber=-9999;varwill=newPerson("Will",28);console.log(will.MaxNumber);//9999console.log(will.MinNumber);//undefined本例中分别为两个原型对象添加了“Person.prototype”和“Person.__proto__”MaxNumber”和“MinNumber”属性,这里我们需要弄清楚“prototype”和“__proto__”的区别。“Person.prototype”对应Person构造的所有实例的原型,也就是说“Person.prototype”是这些实例原型链的一部分,所以这些实例在进行属性查找时,会引用“Person.prototype”中的属性。属性隐藏通过原型链查找属性时,首先要查找的是对象本身的属性。如果找不到,就会根据原型链继续查找。这样,如果我们想覆盖原型链上的一些属性,我们可以直接在对象中引入这些属性,从而达到属性隐藏的效果。看一个简单的例子:functionPerson(name,age){this.name=name;this.age=age;}Person.prototype.getInfo=function(){console.log(this.name+"is"+this.age+"yearsold");};varwill=newPerson("Will",28);will.getInfo=function(){console.log("getInfomethodfromwillinsteadofprototype");};will.getInfo();//getInfomethodfromwillinsteadofprototype对象创建方法影响原型链会转到本文开头的例子。will对象是通过Person构造函数创建的,所以will(will.__proto__)的原型是“Person.prototype”。同样,我们可以通过以下方式创建一个对象:varJuly={name:"July",age:28,getInfo:function(){console.log(this.name+"is"+this.age+"yearsold");},}console.log(July.getInfo());当使用该方法创建对象时,原型链变成如下图,July对象的原型为“Object.prototype”,说明对象的构造方式影响原型链的形式。hasOwnProperty“hasOwnProperty”是“Object.prototype”的一个方法,可以判断一个对象是否包含自定义属性,而不是原型链上的属性,因为“hasOwnProperty”是JavaScript中唯一不查找原型函数的属性连锁,链条。相信大家还记得,在文章的第一个例子中,我们可以通过will访问属性“constructor”,得到will的构造函数Person。结合这里的“hasOwnProperty”函数,可以看出will对象是没有“constructor”属性的。从下面的输出可以看出,“constructor”是will的原型(will.__proto__)的一个属性,但是通过原型链的查找,will对象可以发现并使用“constructor”属性。“hasOwnProperty”还有一个重要的使用场景,就是用来遍历对象的属性。functionPerson(name,age){this.name=name;this.age=age;}Person.prototype.getInfo=function(){console.log(this.name+"is"+this.age+"yearsold");};varwill=newPerson("Will",28);for(varattrinwill){console.log(attr);}//name//age//getInfofor(varattrinwill){if(will.hasOwnProperty(attr)){console.log(attr);}}//name//age总结本文介绍JavaScript中原型相关的概念。对于原型,可以总结出以下几点:所有对象都有“[[prototype]]”属性(通过__proto__access),这个属性对应对象的原型。所有的函数对象都有一个“prototype”属性,这个属性的值会被赋值给函数创建的对象的“__proto__”属性。所有原型对象都有一个“构造函数”属性。该属性对应于构造函数对象和创建所有指向原型的实例的原型对象。“原型”和“构造函数”属性相互关联。同样重要的是要强调文章开头的例子和通过例子得到的“General”。object”、“functionobject”和“prototypeobject”,当你对原型之间的关系感到困惑时,只要想想这张图(或者重新画一张当前对象的关系图),你就能明白这里复杂的关系。通过这些介绍,相信大家可以对原型有一个清晰的认识。