類聲明以下所示:javascript
var Cat = { name : '', color : '' }
若是咱們要聲明Cat類的實例的話,則代碼以下所示:html
var cat1 = {}; // 建立一個空對象 cat1.name = "大毛"; // 按照原型對象的屬性賦值 cat1.color = "黃色"; var cat2 = {}; cat2.name = "二毛"; cat2.color = "黑色";
可是這樣有兩個顯著的缺點:java
事實上構造函數跟普通函數沒有任何區別,惟一的技巧在於內部使用了this變量,這樣對對構造函數使用new運算符,就能生成實例,而後this變量會綁定在實例對象上。編程
那麼上述的代碼咱們可使用構造函數這樣來寫:app
function Cat(name,color){//構造函數 this.name=name; this.color=color; this.type = "貓科動物"; this.eat = function(){alert("吃老鼠");}; } //聲明類實例 var cat1 = new Cat("大毛","黃色"); var cat2 = new Cat("二毛","黑色");//cat1和cat2會自動含有一個constructor屬性,指向它們的構造函數。 console.log(cat1.constructor == Cat); //true console.log(cat2.constructor == Cat); //true //Javascript還提供了一個instanceof運算符,驗證原型對象與實例對象之間的關係。 console.log(cat1 instanceof Cat); //true console.log(cat2 instanceof Cat); //true
你們有沒有想過構造函數的缺點,那就是資源浪費。如上述代碼所示,那麼每一個對象都會有一個屬於本身的name和color屬性,對於屬性這樣是無可厚非,但是若是是方法呢,每一個對象都會有一個屬於本身的但實現相同功能的方法,這樣難道不是大大的資源浪費嗎?函數
你們注意到上面的代碼「cat1.constructor == Cat」 this
解釋一下哦:任何一個prototype對象都有一個constructor屬性,指向它的構造函數。每個實例也有一個constructor屬性,默認調用prototype對象的constructor屬性。spa
構造函數綁定,即用call(apply)把父對象的this指向改成子對象,prototype
Javascript規定,每個構造函數都有一個prototype屬性,指向另外一個對象。這個對象的全部屬性和方法,都會被構造函數的實例繼承。這樣咱們就能夠把全部的屬性放在構造函數當中,而把全部的方法放在Prototype上。插件
因此上述代碼能夠以下使用,
function Cat(name,color){//聲明類 this.name = name;//全部屬性都放在構造函數裏面 this.color = color; } //全部方法都放在prototype鏈上 Cat.prototype.type = "貓科動物"; Cat.prototype.eat = function(){alert("吃老鼠")}; var cat1 = new Cat("大毛","黃色"); var cat2 = new Cat("二毛","黑色"); console.log(cat1.eat == cat2.eat); //true 這個時候不一樣實例共享原型鏈上的同一個方法
如何檢測對象和實例的關係呢,有以下三種方法
console.log(Cat.prototype.isPrototypeOf(cat1)); //true
alert(cat1.hasOwnProperty("name")); // true
alert("name" in cat1); // true
hasOwnProperty和in惟一的區別就在於若是是從原型鏈上繼承的屬性in也會返回true,而hasOwnProperty則返回false。
上面不僅是介紹說如何聲明類以及實例化類的對象,那下面講講各個類如何繼承。
介紹以下幾種:
構造函數繼承
prototype模式繼承
拷貝繼承
function Animal(){//父類 this.species = "動物"; } function Cat(name,color){//子類 Animal.apply(this, arguments);//這句話就實現了繼承 使用call或apply方法,將父對象的構造函數綁定在子對象上 this.name = name; this.color = color; }
function Animal(){//父類 this.species = "動物"; } function Cat(name,color){//子類 this.name = name; this.color = color; } Cat.prototype = new Animal(); Cat.prototype.constructor = Cat;
prototype模式繼承的經典在於讓子類的prototype指向父類的實例,由於每一個prototype對象都會有一個constuctor來指向它的構造函數,而上述的「Cat.prototype = new Animal();」這句代碼就使得prototype的constructor屬性指向了Animal,你們想一想這樣不就亂套了嗎,因此才用了「Cat.prototype.constructor = Cat;」來修正
你們想一想上述代碼有什麼缺點,那就是每一個子類都必須聲明一個父類的實例,這樣啓不是資源的浪費。因此咱們若是使用prototype模式來繼承通常會使用以下方法。
function extend(Child, Parent) { var F = function(){};//聲明一個空對象 F.prototype = Parent.prototype;//把空對象的原型鏈指向父對象的原型鏈 事實上F類就至關於Parent類 Child.prototype = new F();//依舊子對象的prototype爲父對象的實例 只不過這時是一個空對象而已 節省了資源 Child.prototype.constructor = Child;//修正原型鏈 Child.uber = Parent.prototype;//在子對象上打開一條通道,能夠直接調用父對象的方法。這一行放在這裏,只是爲了實現繼承的完備性,純屬備用性質 } //使用的時候以下所示 extend(Cat,Animal);//Cat繼承Animal var cat1 = new Cat("大毛","黃色");//實例化對象
function extend(Child, Parent) { var p = Parent.prototype; var c = Child.prototype; for (var i in p) { c[i] = p[i]; } c.uber = p; }
拷貝繼承的方式有些相似其它語言如C#的思想,循環遍歷屬性而後給另一個對象賦值,彷佛這樣也能實現繼承,可是你們有沒有想過上述方法的弊端,若是屬性是引用類型的呢,那是否是又亂了,修改了一個實例的引用類型的屬性就致使其它對象的相應屬性的值改變,這樣的繼承只能說是不完全的。那麼咱們應該怎麼樣實現深度拷貝繼承呢
function deepCopy(p, c) { var c = c || {}; for (var i in p) { if (typeof p[i] === 'object') { c[i] = (p[i].constructor === Array) ? [] : {}; deepCopy(p[i], c[i]); } else { c[i] = p[i]; } } return c; }
事實上JQuery庫就是使用的深拷貝繼承,你們能夠想一想爲何,由於拷貝繼承很適合傳遞參數,你們想一想編寫jQuery插件時,寫一些配置參數的時候是否是以下使用,結合想一想會不會明白些許
options = $.extend({ name: 'Default Name', age: 22, man: true }, options);
function object(o) { function F() {}//臨時聲明一個構造函數 使這個構造函數的PROTOTYPE屬性指向父對象,從而使得子對象與父對象連在一塊兒,而後生成一個實例並返回把子對象的prototype屬性, F.prototype = o; return new F(); }
參考文獻爲阮大師的 JavaScript面向對象編程系列
最近發現阮大師的文章真的很值得一看,你們有空能夠看看,他的文采很好,「散文」和「觀點和感想」類的文章不少都寫的很不錯。真心有些小佩服他了,不過與其佩服別人不如作本身佩服的人。