原型是JavaScript中一个比较难理解的概念,原型相关的属性也比较多,对象有"[[prototype]]"属性,函数对象有"prototype"属性,原型对象有"constructor"属性。javascript
为了弄清原型,以及原型相关的这些属性关系,就有了这篇文章。html
相信经过这篇文章必定可以清楚的认识到原型,如今就开始原型之旅吧。java
开始原型的介绍以前,首先来认识一下什么是原型?数组
在JavaScript中,原型也是一个对象,经过原型能够实现对象的属性继承,JavaScript的对象中都包含了一个" [[Prototype]]"内部属性,这个属性所对应的就是该对象的原型。浏览器
"[[Prototype]]"做为对象的内部属性,是不能被直接访问的。因此为了方便查看一个对象的原型,Firefox和Chrome中提供了"__proto__"这个非标准(不是全部浏览器都支持)的访问器(ECMA引入了标准对象原型访问器"Object.getPrototype(object)")。函数
function Person(name, age){ this.name = name; this.age = age; this.getInfo = function(){ console.log(this.name + " is " + this.age + " years old"); }; } var will = new Person("Will", 28);
在上面的代码中,经过了Person这个构造函数建立了一个will对象。下面就经过will这个对象一步步展开了解原型。this
Step 1: 查看对象will的原型spa
经过下面代码,能够查看对象will的原型:prototype
console.log(will.__proto__); console.log(will.constructor);
结果分析:htm
经过"constructor"这个属性,咱们能够来判断一个对象是否是数组类型
function isArray(myArray) { return myArray.constructor.toString().indexOf("Array") > -1; }
Step 2: 查看对象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.constructor === Person);
结果分析:
经过上面能够看到,"Person.prototype"对象和Person函数对象经过"constructor"和"prototype"属性实现了相互引用(后面会有图展现这个相互引用的关系)。
Step 3: 查看对象Object的原型
经过前一部分能够看到,will的原型的原型是"Object {}"对象。实际上在JavaScript中,全部对象的原型都将追溯到"Object {}"对象。
下面经过一段代码看看"Object {}"对象:
console.log(Person.prototype.__proto__ === Object.prototype); console.log(typeof Object); console.log(Object); console.log(Object.prototype); console.log(Object.prototype.__proto__); console.log(Object.prototype.constructor);
经过下面的代码能够看到:
Step 4: 查看对象Function的原型
在上面的例子中,Person是一个构造函数,在JavaScript中函数也是对象,因此,咱们也能够经过"__proto__"属性来查找Person函数对象的原型。
console.log(Person.__proto__ === Function.prototype); console.log(Person.constructor === Function) console.log(typeof Function); console.log(Function); console.log(Function.prototype); console.log(Function.prototype.__proto__); console.log(Function.prototype.constructor);
结果分析 :
对于"prototype"和"__proto__"这两个属性有的时候可能会弄混,"Person.prototype"和"Person.__proto__"是彻底不一样的。
在这里对"prototype"和"__proto__"进行简单的介绍:
经过上面结合实例的分析,相信你必定了解了原型中的不少内容。
可是如今确定对上面例子中的关系感受很凌乱,一下子原型,一下子原型的原型,还有Function,Object,constructor,prototype等等关系。
如今就对上面的例子中分析获得的结果/关系进行图解,相信这张图可让你豁然开朗。
对于上图的总结以下:
在上面例子中,"getInfo"方法是构造函数Person的一个成员,当经过Person构造两个实例的时候,每一个实例都会包含一个"getInfo"方法。
var will = new Person("Will", 28); var wilber = new Person("Wilber", 27);
前面了解到,原型就是为了方便实现属性的继承,因此能够将"getInfo"方法看成Person原型(Person.__proto__)的一个属性,这样全部的实例均可以经过原型继承的方式来使用"getInfo"这个方法了。
因此对例子进行以下修改:
function Person(name, age){ this.name = name; this.age = age; } Person.prototype.getInfo = function(){ console.log(this.name + " is " + this.age + " years old"); };
由于每一个对象和原型都有原型,对象的原型指向对象的父,而父的原型又指向父的父,这种原型层层链接起来的就构成了原型链。
在"理解JavaScript的做用域链"一文中,已经介绍了标识符和属性经过做用域链和原型链的查找。
这里就继续看一下基于原型链的属性查找。
当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止,到查找到达原型链的顶部(也就是 "Object.prototype"), 若是仍然没有找到指定的属性,就会返回 undefined。
function Person(name, age){ this.name = name; this.age = age; } Person.prototype.MaxNumber = 9999; Person.__proto__.MinNumber = -9999; var will = new Person("Will", 28); console.log(will.MaxNumber); // 9999 console.log(will.MinNumber); // undefined
在这个例子中分别给"Person.prototype "和" Person.__proto__"这两个原型对象添加了"MaxNumber "和"MinNumber"属性,这里就须要弄清"prototype"和"__proto__"的区别了。
"Person.prototype "对应的就是Person构造出来全部实例的原型,也就是说"Person.prototype "属于这些实例原型链的一部分,因此当这些实例进行属性查找时候,就会引用到"Person.prototype "中的属性。
当经过原型链查找一个属性的时候,首先查找的是对象自己的属性,若是找不到才会继续按照原型链进行查找。
这样一来,若是想要覆盖原型链上的一些属性,咱们就能够直接在对象中引入这些属性,达到属性隐藏的效果。
看一个简单的例子:
function Person(name, age){ this.name = name; this.age = age; } Person.prototype.getInfo = function(){ console.log(this.name + " is " + this.age + " years old"); }; var will = new Person("Will", 28); will.getInfo = function(){ console.log("getInfo method from will instead of prototype"); }; will.getInfo(); // getInfo method from will instead of prototype
会到本文开始的例子,will对象经过Person构造函数建立,因此will的原型(will.__proto__)就是"Person.prototype"。
一样,咱们能够经过下面的方式建立一个对象:
var July = { name: "July", age: 28, getInfo: function(){ console.log(this.name + " is " + this.age + " years old"); }, } console.log(July.getInfo());
当使用这种方式建立一个对象的时候,原型链就变成下图了,July对象的原型是"Object.prototype"也就是说对象的构建方式会影响原型链的形式。
"hasOwnProperty"是"Object.prototype"的一个方法,该方法能判断一个对象是否包含自定义属性而不是原型链上的属性,由于"hasOwnProperty" 是 JavaScript 中惟一一个处理属性可是不查找原型链的函数。
相信你还记得文章最开始的例子中,经过will咱们能够访问"constructor"这个属性,并获得will的构造函数Person。这里结合"hasOwnProperty"这个函数就能够看到,will对象并无"constructor"这个属性。
从下面的输出能够看到,"constructor"是will的原型(will.__proto__)的属性,可是经过原型链的查找,will对象能够发现并使用"constructor"属性。
"hasOwnProperty"还有一个重要的使用场景,就是用来遍历对象的属性。
function Person(name, age){ this.name = name; this.age = age; } Person.prototype.getInfo = function(){ console.log(this.name + " is " + this.age + " years old"); }; var will = new Person("Will", 28); for(var attr in will){ console.log(attr); } // name // age // getInfo for(var attr in will){ if(will.hasOwnProperty(attr)){ console.log(attr); } } // name // age
本文介绍了JavaScript中原型相关的概念,对于原型能够概括出下面一些点:
还有要强调的是文章开始的例子,以及经过例子获得的一张"普通对象","函数对象"和"原型对象"之间的关系图,当你对原型的关系迷惑的时候,就想一想这张图(或者重画一张当前对象的关系图),就能够理清这里面的复杂关系了。
经过这些介绍,相信必定能够对原型有个清晰的认识。