# javascript面向對象(一)
對象其實就是一種引用類型。而對象的值就是引用類型的實例。在JavaScript 中引用類型是一種數據結構,將數據和功能組織在一塊兒。它也常被稱作爲類,但JavaScript 中卻沒有類的概念。雖然JavaScript 是一門面向對象的語言,卻不具有傳統面嚮對象語言所支持的類和接口等基本結構。
## 對象的建立以及經常使用操做 ##
1. **使用new運算符**
javascript
var user = new Object(); //使用new運算符建立一個對象 user.name = 'tina; //給對象添加屬性 user.age = 22; user.address = '四川成都';
2. **對象字面量(JSON方式)**
java
var user = { name:'編程浪子', age:22, address:'四川成都' };
3. **簡單方式(傳統賦值方式)**
程序員
var user = {}; user.name = 'tina'; //給對象添加屬性 user.age = 22; user.address = '四川成都';
4. **屬性的調用**
對於對象屬性的調用有兩種方式:
調用方法以下:
alert(user.name + " " +user.age);//返回 '編程浪子 四川成都'
另外一種方法:
alert(user['name'] + " " +user['age']);//返回 '編程浪子 四川成都'
5. **添加方法**
編程
var user = { name:'tina', //給對象添加屬性 age:22, address:'四川成都', showInfo:function(){//添加一個方法 alert(this.name+" "+this.age+" "+this.address); }, showHello:showHello//將對象外部的方法添加到對象 }; function showHello(){ alert("Hello!"); } user.showInfo();//調用方法 user.showHello();
# javascript面向對象(二) #
## 建立對象 ##
咱們知道,要建立一個對象咱們能夠用以下代碼:
數組
var user = new Object(); user.name = '編程浪子'; user.age = 22; user.address = '四川成都';
用這樣的方法建立對象比較簡單直觀,也是JavaScript種建立對象最基本的方法。可是這樣就有一個問題,若是咱們須要建立多個對象,那麼我就得寫不少重複的代碼。好比咱們想建立另外一個對象user1,咱們就得從新將上面的代碼從新寫一遍,這在實際開發過程當中是不合適的,這樣若是對象過多,代碼量將大大增長。
爲了解決這樣的問題,咱們可使用一種叫作**工廠模式**的方法,這種方法 就是爲了解決實例化對象產生大量重複代碼的問題。
## 工廠模式 ##
瀏覽器
1 function create(name, age) { 2 3 var obj = new Object(); 4 5 obj.name = name; 6 7 obj.age = age; 8 9 obj.show = function () { 10 11 return this.name +' '+ this.age; 12 13 }; 14 15 return obj; 16 17 } 18 19 var obj1= create('bclz', 30); //第一個實例 20 21 var obj2= create('bcxb', 20); //第二個實例 22 23 alert(obj1.show()); 24 25 alert(obj2.show());
從上面的代碼咱們能夠看出,工廠模式解決了實例化時代碼大量重複的問題,但又出現了一個問題,那就是識別問題,咱們根本沒法弄清楚他們究竟是哪一個對象的實例。好比
數據結構
alert(typeof obj1); //Object alert(obj1 instanceof Object); //true
以上代碼標明obj1是Object對象,可是咱們沒法知道具體是哪個對象建立的。
## 構造函數(構造方法) ##
函數
1 function User(name, age) { //構造函數模式 2 3 this.name = name; 4 5 this.age = age; 6 7 this.show = function () { 8 9 return this.name + ' '+this.age; 10 11 }; 12 13 }
建立對象的時候用new運算符就能夠了:
測試
var user1 = new User('bclz', 30); //第一個實例 var user2 = new User('bcxb', 20); //第二個實例
如今咱們就能夠檢測user1或者user2是否是屬於User。
this
alert(user1 instanceof User);//true
可見,使用構造函數的方法,即解決了**重複實例化**的問題,又解決了**對象識別**的問題。
要建立User對象的新實例,就要使用new操做符,使用這個方式構建實例對象,會通過下面4個步驟:
1.建立一個新對象;
2.將構造函數的做用域給新對象(所以this指向的這個新對象)。
3.執行構造函數內的代碼在(爲新對象添加屬性);
4.返回新對象。
不過須要注意下面兩個問題:
1):構造函數也是函數**
構造函數與函數的惟一區別,就是調用方式的不一樣,不過,構造函數畢竟也是函數,不存在什麼特殊的定義構造函數的語法。任何函數,只要經過new操做符來調用,就能夠把它看做是構造函數;而任何函數,若是不經過new操做符調用,它就和普通函數沒有什麼區別,例如前面定義的User:
1 //看成構造函數調用 2 3 var user1 = new User('bclz', 30); 4 5 user1.show(); //bclz 30; 6 7 8 9 //看成普通函數調用 10 11 User('bclz', 30); 12 13 window.show(); //bclz 30;
結果上沒有什麼區別,只是你們能夠看到,看成普通函數調用的話,函數裏this對象的指向,實際上是指向的window全局對象。而經過new關鍵字調用,this指向的則是新對象而已,因此,其實還能夠這麼來寫:
var o = new Object(); User.call(o,'bclz', 30); o.show();
經過函數方法call來從新定義對象的做用域,這裏很少作解釋,講到函數細節時再仔細介紹這種方法,這裏只是說明能夠改變對象的做用域的,其實就是**改變this的指向**
**2):構造函數的問題**
構造函數的模式雖然好,可是並不是沒有缺點。構造函數最大的問題就是,每一個方法都要在實例上從新建立一次。在前面的例子中,user1和user2中都有一個show方法,若是咱們執行如下語句:
alert(user1.show==user2.show);//結果返回的是false
結果返回的是false,這就說明方法其實也是一種引用地址。若是咱們一樣重複建立了多個對象,那麼每一個對象中的方法都會在內存中開闢新的空間,這樣浪費的空間就比較多。要解決這個問題,咱們就須要用到實例屬性或者方法的共享。 咱們可使用一種變通的方式,來達到咱們想要的效果,也就是讓show方法再也不重複建立
1 function User(name, age) { 2 3 this.name = name; 4 5 this.age = age; 6 7 this.show = show; 8 9 } 10 11 function show(){ 12 13 alert(this.name + ' ' + this.age); 14 15 }
將show方法移到外部,至關於show方法成了一個全局函數,而後再到User構造函數內部去引用show方法,這樣User內部的this.show都指向了同一個全局函數show,所以,咱們實例化的user1和user2就實現了共享,能夠再次調用:
alert(user1.show==user2.show);//結果返回的是true
可是這只是一個測試,若是你要讓更多的屬性或者方法實現共享,那不是要定義更多的全局函數或者變量,這種方式是不科學也不可行的。所以,咱們須要引入另一個javascript面向對象的重要概念**原型**
# javascript面向對象(三) #
## Prototype原型模式 ##
## 經過構造函數的弊端引出原型概念 ##
爲了講清楚原型,咱們仍是必須先回顧一下構造函數的問題,用一個簡單的例子再次講解一下,咱們有一隻貓的構造函數,以下:
1 function Cat(name,color){ 2 this.name = name; 3 this.color = color; 4 }
這個構造函數很簡單,再也不多說,那麼如今再爲這個函數添加一個不變的屬性"type"(種類),再添加一個方法eat(吃老鼠)。那麼,Cat就變成了下面這樣:
1 function Cat(name,color){ 2 this.name = name; 3 this.color = color; 4 this.type = "貓科動物"; 5 this.eat = function(){alert("吃老鼠");}; 6 } 7 8 9 //生成實例: 10 11 var cat1 = new Cat("大毛","黃色"); 12 var cat2 = new Cat ("二毛","黑色"); 13 alert(cat1.type); // 貓科動物 14 cat1.eat(); // 吃老鼠
表面上好像沒什麼問題,可是實際上這樣作,有一個很大的弊端。那就是對於每個實例對象,type屬性和eat()方法都是如出一轍的內容,每一次生成一個實例,都必須爲重複的內容,多佔用一些內存。這樣既不環保,也缺少效率。
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構造函數,將它修改一下:
1 function Cat(name,color){ 2 3 this.name = name; 4 5 this.color = color; 6 7 } 8 9 Cat.prototype.type = "貓科動物"; 10 11 Cat.prototype.eat = function(){alert("吃老鼠")}; 12 13 //生成實例: 14 15 16 17 var cat1 = new Cat("大毛","黃色"); 18 19 var cat2 = new Cat("二毛","黑色"); 20 21 alert(cat1.type); // 貓科動物 22 23 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高級程序設計** 書中對與原型的描述和說明
1 function Person(){ } //建立Person構造函數 2 3 Person.prototype.name = "Nicholas";//建立共享屬性name 4 5 Person.prototype.age = 29; //建立共享屬性age 6 7 Person.prototype.job = "Software Engineer"; //建立共享屬性job 8 9 Person.prototype.sayName = function(){ //建立共享函數sayName 10 11 alert(this.name); 12 13 }; 14 15 16 17 //分別建立了person1和person2,裏面都有sayName函數,而且彈出的值都是同樣 18 19 var person1 = new Person(); 20 21 person1.sayName(); //"Nicholas" 22 23 var person2 = new Person(); 24 25 person2.sayName(); //"Nicholas" 26 27 alert(person1.sayName == person2.sayName); //true
經過上面的圖,能夠看到,person1和person2,他們內部都有一個指向Person.prototype的指針,能夠經過原型的isPrototype方法測試一下
1 alert(Person.prototype.isPrototypeOf(person1)); //true 2 3 alert(Person.prototype.isPrototypeOf(person2)); //true 4 5 function User(){}; 6 7 var person3 = new User(); 8 9 alert(Person.prototype.isPrototypeOf(person3)); //false 10 11 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原型,如今主要來看看,經過原型來建立對象的幾種方式
### 基本原型 ###
1 function Person(){ } 2 3 Person.prototype.name = "Nicholas"; 4 5 Person.prototype.age = 29; 6 7 Person.prototype.job = "Software Engineer"; 8 9 Person.prototype.sayName = function(){ 10 11 alert(this.name); 12 13 };
固然這種方式只是說明原型的道理,實際使用中不多把屬性寫在原型中
### 更簡單的方式 ###
1 function Person(){ } 2 Person.prototype = { 3 4 name : "Nicholas", 5 6 age : 29, 7 8 job: "Software Engineer", 9 10 sayName : function () { 11 12 alert(this.name); 13 14 } 15 16 };
這種方式只是上面方式的簡單寫法,經過對象字面量直接寫完全部屬性。效果和上面的寫法是同樣的,只是寫法不同。
可是直接所有把屬性和方法所有寫在原型中,這並不現實,看下面的列子:
1 function Person(){ } 2 3 4 5 Person.prototype = { 6 7 constructor: Person, 8 9 name : "Nicholas", 10 11 age : 29, 12 13 job : "Software Engineer", 14 15 friends : ["Shelby", "Court"], 16 17 sayName : function () { 18 19 alert(this.name); 20 21 } 22 23 }; 24 25 26 27 var person1 = new Person(); 28 29 var person2 = new Person(); 30 31 person1.friends.push("Van"); 32 33 alert(person1.friends); //"Shelby,Court,Van" 34 35 alert(person2.friends); //"Shelby,Court,Van" 36 37 alert(person1.friends === person2.friends); //true
上面的列子很容易看出,講屬性寫在原型中的問題,列子中的friends是個數組,引用數據類型在person1中修改了friends,添加了一個條數據以後,能夠看到person1和person2對象的friends都發生了改變,這其實很好理解,由於prototype對象自己就是共享的,數組又是屬於引用類型,改變了一個,其餘的都會發生改變。
因此,在實際中使用的更多的方法是構造函數與原型結合的方式
### 構造函數與原型結合的方式 ###
1 function Person(name, age, job){ 2 3 this.name = name; 4 5 this.age = age; 6 7 this.job = job; 8 9 this.friends = ["Shelby", "Court"]; 10 11 } 12 13 Person.prototype = { 14 15 constructor : Person, 16 17 sayName : function(){ 18 19 alert(this.name); 20 21 } 22 23 } 24 25 26 27 var person1 = new Person("Nicholas", 29, "Software Engineer"); 28 29 var person2 = new Person("Greg", 27, "Doctor"); 30 31 person1.friends.push("Van"); 32 33 alert(person1.friends); //"Shelby,Count,Van" 34 35 alert(person2.friends); //"Shelby,Count" 36 37 alert(person1.friends === person2.friends); //false 38 39 alert(person1.sayName === person2.sayName); //true
這裏就能夠看到,friends的屬性在兩個對象中就算改變了其中一個,並不會對另一個產生影響。這種構造函數加原型的混成模式,是目前使用率,承認率最高的一種自定義類型的方式,因此,通常狀況下,咱們定義自定義類型默認都使用這種模式
### 動態原型模式 ###
這種模式只是上面模式的變種,對於一些習慣書寫面嚮對象語言的程序員來講,一個類要分開兩個部分來寫,是很是不習慣的,因此,就有了動態原型模式,其實無非就是,把以前分開兩部分寫的內容,所有提到函數中,加上判斷就好了
1 function Person(name, age, job){ 2 3 4 5 //屬性 6 7 this.name = name; 8 9 this.age = age; 10 11 this.job = job; 12 13 14 15 //方法 16 17 if (typeof this.sayName != "function"){ 18 19 Person.prototype.sayName = function(){ 20 21 alert(this.name); 22 23 }; 24 25 } 26 27 } 28 29 30 31 var friend = new Person("Nicholas", 29, "Software Engineer"); 32 33 friend.sayName();
注意上面的判斷,這種方式只有在sayName函數不存在的狀況下,纔會將它添加到原型中,若是sayName函數已經存在,那麼這段代碼就不會再運行,並且就算有不少方法的話,if語句也不用所有判斷,只是須要判斷一個就好了。 這樣的寫法,對於java或者C#程序員相對來講感官上比較容易接受,並且寫法也沒有任何缺陷。可是,有一點不算是缺陷的缺點,javascript是一門動態語言,也就是說,屬性和方法是隨時能夠添加的,若是所有寫在構造函數裏面去,反而看起來不是那麼的靈活。因此,通常狀況下,使用構造函數與原型的混合模式的仍是比較多的