爲了講清楚原型,咱們仍是必須先回顧一下構造函數的問題,用一個簡單的例子再次講解一下,咱們有一隻貓的構造函數,以下:javascript
function Cat(name,color){ this.name = name; this.color = color; }
這個構造函數很簡單,再也不多說,那麼如今再爲這個函數添加一個不變的屬性"type"(種類),再添加一個方法eat(吃老鼠)。那麼,Cat就變成了下面這樣:java
function Cat(name,color){ this.name = name; this.color = color; this.type = "貓科動物"; this.eat = function(){alert("吃老鼠");}; }
生成實例:程序員
var cat1 = new Cat("大毛","黃色"); var cat2 = new Cat ("二毛","黑色"); alert(cat1.type); // 貓科動物 cat1.eat(); // 吃老鼠
表面上好像沒什麼問題,可是實際上這樣作,有一個很大的弊端。那就是對於每個實例對象,type屬性和eat()方法都是如出一轍的內容,每一次生成一個實例,都必須爲重複的內容,多佔用一些內存。這樣既不環保,也缺少效率。web
alert(cat1.eat == cat2.eat); //false
所以,爲了讓type屬性和eat()方法在內存中只生成一次,而後全部實例都指向那個內存地址,引出了原型數組
原型對象實際上就是構造函數的一個實例對象,和普通的實例對象沒有本質上的區別。能夠包含特定類型的全部實例的共享屬性或者方法。 這個prototype的屬性值是一個對象(屬性的集合),默認的只有一個叫作constructor的屬性,指向這個函數自己。瀏覽器
好比咱們簡單定義一個SuperType名字的函數,裏面什麼屬性也沒有在函數內部是這個樣子的函數
function SuperType(){ }
從上圖咱們看到,函數裏面雖然什麼都沒有,可是有一個默認的prototype屬性,它是一個對象,它指向的是本身的地址,而prototype這個對象自己裏面又有一個屬性constructor,而這個屬性,又指向了函數自己,有點繞,你能夠經過下面的代碼作一下測試,看看效果測試
alert(SuperType.prototype) //object alert(SuperType.prototype.constructor) //彈出函數自己function SuperType(){}
prototype和constructor是原型最基本的概念,如今看可能還有點暈,不要緊,我直接上之前的代碼,看看區別,仍是以前的Cat構造函數,將它修改一下:this
function Cat(name,color){ this.name = name; this.color = color; } Cat.prototype.type = "貓科動物"; Cat.prototype.eat = function(){alert("吃老鼠")};
生成實例:spa
var cat1 = new Cat("大毛","黃色"); var cat2 = new Cat("二毛","黑色"); alert(cat1.type); // 貓科動物 cat1.eat(); // 吃老鼠
這時全部實例的type屬性和eat()方法,其實都是同一個內存地址,指向prototype對象,所以就提升了運行效率。
alert(cat1.eat == cat2.eat); //true
爲了配合prototype屬性,Javascript定義了一些輔助方法,幫助咱們使用它。
isPrototypeOf()
這個方法用來判斷,某個proptotype對象和某個實例之間的關係。
alert(Cat.prototype.isPrototypeOf(cat1)); //true alert(Cat.prototype.isPrototypeOf(cat2)); //true
hasOwnProperty()
每一個實例對象都有一個hasOwnProperty()方法,用來判斷某一個屬性究竟是本地屬性,仍是繼承自prototype對象的屬性。
alert(cat1.hasOwnProperty("name")); // true alert(cat1.hasOwnProperty("type")); // false
in運算符
in運算符能夠用來判斷,某個實例是否含有某個屬性,不論是不是本地屬性。
alert("name" in cat1); // true alert("type" in cat1); // true
in運算符還能夠用來遍歷某個對象的全部屬性。
for(var prop in cat1) { alert("cat1["+prop+"]="+cat1[prop]); }
來看一下 javascript高級程序設計 書中對與原型的描述和說明
function Person(){ } //建立Person構造函數 Person.prototype.name = "Nicholas";//建立共享屬性name Person.prototype.age = 29; //建立共享屬性age Person.prototype.job = "Software Engineer"; //建立共享屬性job Person.prototype.sayName = function(){ //建立共享函數sayName alert(this.name); }; //分別建立了person1和person2,裏面都有sayName函數,而且彈出的值都是同樣 var person1 = new Person(); person1.sayName(); //"Nicholas" var person2 = new Person(); person2.sayName(); //"Nicholas" alert(person1.sayName == person2.sayName); //true
經過上面的圖,能夠看到,person1和person2,他們內部都有一個指向Person.prototype的指針,能夠經過原型的isPrototype方法測試一下
alert(Person.prototype.isPrototypeOf(person1)); //true alert(Person.prototype.isPrototypeOf(person2)); //true function User(){}; var person3 = new User(); alert(Person.prototype.isPrototypeOf(person3)); //false alert(User.prototype.isPrototypeOf(person3)); //true
上面咱們建立了兩個對象,person1和person2,這兩個對象,也都指向了Person構造函數的原型,這是由於每一個對象都有一個隱藏的屬性——「_proto_」,這個屬性引用了建立這個對象的函數的prototype。即:person1._proto_ === Person.prototype
這個_proto_是一個隱藏的屬性,javascript不但願開發者用到這個屬性值,有的低版本瀏覽器甚至不支持這個屬性值。看下面的代碼:
console.log(Object.prototype); var obj = new Object(); console.log(obj.__proto__);
你會發現打印了相同的內容:
obj這個對象本質上是被Object函數建立的,所以obj._proto_=== Object.prototype。咱們能夠用一個圖來表示。
關於隱式原型,主要涉及到原型繼承的主要原理
上一章羅列一直知識點,可是主要是爲了說明prototype原型,如今主要來看看,經過原型來建立對象的幾種方式
function Person(){ } Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){ alert(this.name); };
固然這種方式只是說明原型的道理,實際使用中不多把屬性寫在原型中
function Person(){ } Person.prototype = { name : "Nicholas", age : 29, job: "Software Engineer", sayName : function () { alert(this.name); } };
這種方式只是上面方式的簡單寫法,經過對象字面量直接寫完全部屬性。效果和上面的寫法是同樣的,只是寫法不同。
可是直接所有把屬性和方法所有寫在原型中,這並不現實,看下面的列子:
function Person(){ } Person.prototype = { constructor: Person, name : "Nicholas", age : 29, job : "Software Engineer", friends : ["Shelby", "Court"], sayName : function () { alert(this.name); } }; var person1 = new Person(); var person2 = new Person(); person1.friends.push("Van"); alert(person1.friends); //"Shelby,Court,Van" alert(person2.friends); //"Shelby,Court,Van" alert(person1.friends === person2.friends); //true
上面的列子很容易看出,講屬性寫在原型中的問題,列子中的friends是個數組,引用數據類型在person1中修改了friends,添加了一個條數據以後,能夠看到person1和person2對象的friends都發生了改變,這其實很好理解,由於prototype對象自己就是共享的,數組又是屬於引用類型,改變了一個,其餘的都會發生改變。
因此,在實際中使用的更多的方法是構造函數與原型結合的方式
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.friends = ["Shelby", "Court"]; } Person.prototype = { constructor : Person, sayName : function(){ alert(this.name); } } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor"); person1.friends.push("Van"); alert(person1.friends); //"Shelby,Count,Van" alert(person2.friends); //"Shelby,Count" alert(person1.friends === person2.friends); //false alert(person1.sayName === person2.sayName); //true
這裏就能夠看到,friends的屬性在兩個對象中就算改變了其中一個,並不會對另一個產生影響。這種構造函數加原型的混成模式,是目前使用率,承認率最高的一種自定義類型的方式,因此,通常狀況下,咱們定義自定義類型默認都使用這種模式
這種模式只是上面模式的變種,對於一些習慣書寫面嚮對象語言的程序員來講,一個類要分開兩個部分來寫,是很是不習慣的,因此,就有了動態原型模式,其實無非就是,把以前分開兩部分寫的內容,所有提到函數中,加上判斷就好了
function Person(name, age, job){ //屬性 this.name = name; this.age = age; this.job = job; //方法 if (typeof this.sayName != "function"){ Person.prototype.sayName = function(){ alert(this.name); }; } } var friend = new Person("Nicholas", 29, "Software Engineer"); friend.sayName();
注意上面的判斷,這種方式只有在sayName函數不存在的狀況下,纔會將它添加到原型中,若是sayName函數已經存在,那麼這段代碼就不會再運行,並且就算有不少方法的話,if語句也不用所有判斷,只是須要判斷一個就好了。這樣的寫法,對於java或者C#程序員相對來講感官上比較容易接受,並且寫法也沒有任何缺陷。可是,有一點不算是缺陷的缺點,javascript是一門動態語言,也就是說,屬性和方法是隨時能夠添加的,若是所有寫在構造函數裏面去,反而看起來不是那麼的靈活。因此,通常狀況下,使用構造函數與原型的混合模式的仍是比較多的