javascript面向對象(3、四)

javascript面向對象(三)

Prototype原型模式


經過構造函數的弊端引出原型概念

爲了講清楚原型,咱們仍是必須先回顧一下構造函數的問題,用一個簡單的例子再次講解一下,咱們有一隻貓的構造函數,以下: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模式的驗證方法

爲了配合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

對象的_proto_隱式原型

上面咱們建立了兩個對象,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。咱們能夠用一個圖來表示。 

關於隱式原型,主要涉及到原型繼承的主要原理

 

 

javascript面向對象(四)

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是一門動態語言,也就是說,屬性和方法是隨時能夠添加的,若是所有寫在構造函數裏面去,反而看起來不是那麼的靈活。因此,通常狀況下,使用構造函數與原型的混合模式的仍是比較多的

相關文章
相關標籤/搜索