原型是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中原型相關的概念,對於原型能夠概括出下面一些點:
還有要強調的是文章開始的例子,以及經過例子獲得的一張"普通對象","函數對象"和"原型對象"之間的關係圖,當你對原型的關係迷惑的時候,就想一想這張圖(或者重畫一張當前對象的關係圖),就能夠理清這裏面的複雜關係了。
經過這些介紹,相信必定能夠對原型有個清晰的認識。