OOP,簡單說來,就是將萬事萬物歸以爲類。以類去宏觀把握問題的核心。然後再通過 對象 去實踐具體的問題。總之有點只可意會,不可言傳之態。OOP有三大特性,封裝,繼承和多態。
就像一臺電腦,封裝:內部的具體構造被封裝了,只留下鍵盤鼠標等對外操控內部的接口。繼承:比如電腦像添加一個顯示器,直接添加就行了,不用再造一個電腦。多態:首先,一個人,能用一臺電腦做很多事,其次,不同的人,用電腦,也能做很多的事。
瞭解OOP,需要先明白 作用域 和 數據基本類型。作用域即函數運行時,可以開闢的空間,這個空間內的東西,才能用空間內的東西,或者使用更外層的東西。就是盒子套盒子,一層又一層。外面看不見裏面的盒子,裏面卻看得見外面。
其次是類型。基本類型和引用類型。前者包括string,number,boolean等,後者包含function,object等。前者按照值存儲,在棧裏面,後者存儲的是引用,於堆中。
比如下面代碼 pyhotn:
a = hello
b = a
print b #hello 將堆裏面的值複製一份,然後賦值給b
a = world
print b #hello 改變了a的值,但是a和b存儲的值的位置不一樣,無影響
a = ['hello']
b = a
print b # ['hello'] 將a的引用給了b,b通過應用找到a的地址的值
a = ['world']
print b # ['world'] 改變了a的值,b通過引用找到的地址,是改變的那個a
JS OOP
一 創建 “類”和 對象
js沒有類的概念,倒是有一個 Object 類型。js僅僅是基於面對對象。什麼是對象?一切都可以是對象,比如字符串對象,函數對象等。先不去管這麼多。簡單說,對象可以看成是一些具有屬性和方法的數據類型。比如 string有 split方法,有length屬性等。array也有sort方法,有length屬性等。只不過,這些對象都是js自帶。 那麼我們可以自己創造 對象 麼?當然可以。聲明一個變量,幾乎及可以認爲創建了一個對象當然undefined也是一個對象。由於js弱類型和動態性,賦值什麼,就創建什麼類型的對象 。
var obj;
alert(typeof obj); //undefinde
var obj = ''
alert(typeof obj) //string 對象
var obj = [1,2]
alert(typeof obj) //object 對象
但是上述的對象,如果是基本類型,已經存儲在棧裏了,就不能改變了。如果是堆裏的引用類型,就可以再自定義屬性和方法。
var obj = [1,2,3]
alert(obj.length) //3
obj.lengths = 11
alert(obj.lengths) //11
而我們所需要的面對對象的那個對象,基本上都是引用類型。基本類型反而成爲引用類型的屬性。
創建真正的對象
1.直接創建對象
一個是用 花括號 { } 或者使用 new 操作符
var obj = {}
alert(typeof obj) //object
var obj = new Object()
alert(typeof obj) //object
創建了對象 obj,那麼就可以給 obj 添加自定義屬性和方法了
var obj = {}
obj.username = 'python';
obj.age = 13
obj.run = function(){
return this.username + ' ' + this.age + '...';
}
alert(obj.username); //python
alert(obj.run()); //python 13...
也可以寫成 var obj = new Object()的方式創建對象。字面量和new操作符有什麼不同麼?兩者上基本可以替代,稍微不同就是字面量的性能比 new的要好。可是這個 new,簡單的說,new 是一個傳遞者,將 this 指針從new的右邊傳到左邊。比如,obj = new Object()即將 Object的 this 指針,傳個了 obj。此時 obj的this。等同於 Object的 this。new可以傳遞this,call方法也可以傳遞,即對象冒充,後面再說。
上述字面量的方式還可以寫成 ,即字面量的方式創建對象。
var obj = {
username : 'python',
age : 13,
run : function(){
return this.username + ' ' + this.age + '...';
}
}
alert(obj.username); //python
alert(obj.run()); //python 13...
2.通過工廠函數創建對象
直接創建對象的方式,簡單直白。可是,如果想創建另外一個對象,就必須重新寫一個。也就是沒有類的概念。重複的行爲,我們可以放到函數裏面,爲此,可以通過一種叫 工廠函數 的方式創建 “類”。所謂工廠,就是創建一個函數,這個函數就像工廠一樣,能夠源源不斷的生產產品(對象)。通常的工廠,都是三個步驟,選擇源材料,加工,出廠。因此,工廠函數也是一樣,先創建對象,然後給對象加屬性和方法,最後返回對象。
function ObjFactory(username,age){
var obj = new Object(); //創建一個對象 原料 將對象this指針傳給obj
obj.username = username; //加工對象
obj.age = age;
obj.run = function(){
return this.username + ' ' + this.age + '...';
}
return obj; //返回對象
}
var obj = ObjFactory('python',13); //運行工廠函數 傳遞this
alert(obj.username); //python
alert(obj.run()); //python 13...
var obj2 = ObjFactory('ruby',10)
alert(obj2.username); //ruby
alert(obj2.run()); //ruby 10...
alert(obj.run == obj2.run); //false
上述方式,纔有了工廠函數的方式。即可以並且還可以傳遞不同的參數,在工廠函數運行的時候綁定一個對象。將 this 指針傳遞。但是這種方式有一個弊端,即工廠函數每次運行一次。內部的代碼都會執行一邊,也就是,裏面的函數 run都會重新創建一遍。而方法通常是用來共享的,這樣的機制在性能上不好。因此另外一種構造函數的原型模式就能達到這樣的效果。
3.構造函數
構造函數的方法,即是創建一個函數,並不是像工廠函數那樣在函數內創建對象,而是在使用對象的時候,通過 new 構造函數,傳遞 this來創建對象。因爲js中所有的對象都可以看成是繼承 Object。只要能夠將 object的this指針傳遞給變量,那個變量就是對象了。而new不僅能夠創建 Obejct類型,還能傳遞 this。
//創建構造函數
function ObjConstruct(username,age){
this.username = username;
this.age = age;
this.run = function(){
return this.username + ' ' + this.age + '...';
}
}
//實例創建對象
var obj = new ObjConstruct('python',13);
alert(obj.username); //python
alert(obj.run()); //python 13...
var obj2 = new ObjConstruct('ruby',10);
alert(obj2.username); //ruby
alert(obj2.run()); //ruby 12...
alert(obj.run == obj2.run); //false
構造函數比工廠函數的方式,是得 構造函數 更新 OOP中的類。可是,方法還是沒有共享,這就需要原型。
4 原型(prototype)詳解
前面已經提過作用域和基本類型,應用類型的概念。在我們創建一個Object時候(new 一個構造函數),因爲其是引用類型,所以存儲在內存的堆之中。這個作用域創建的時候,構造函數就會有一個原型(prototype)屬性。當 new的付給對象的時候。每一個實例對象 都有一個 __proto__屬性,指向了構造函數的原型。而原型裏又有一個 construct屬性,指向了構造函數本身。
//創建構造函數
function ObjConstruct(){}
//給原型加屬性 方法
ObjConstruct.prototype.username = 'python';
ObjConstruct.prototype.age = 13;
ObjConstruct.prototype.run = function(){
return this.username + ' ' + this.age + '...';
};
//實例創建對象
var obj = new ObjConstruct();
alert(obj.username); //python
alert(obj.run()); //python 13...
如圖所示。一旦new的時候,堆裏面的 prototype就會存在。看下面代碼,就知道 ————__proto__ 和 construct的關係
alert(obj.__proto__); //object Object
alert(ObjConstruct.constructor) //function Function(){ native code}
由上圖可以知道,實例的對象有一個__proto__屬性可以找到構造函數的原型 。因此在原型上加屬性和方法,實例對象的this指針還是能找到,能找到就能調用。而原型又通過 constructor屬性找到構造函數。因爲原型是共享的,那麼可以幫方法寫入原型。而屬性是私有的,則寫入構造函數。反正最終 原型也會通過constructor找到構造函數裏的屬性。將下面代碼改寫
5.混合模式
//混合模式
function ObjConstruct(username,age){
this.username = username;
this.age = age;
}
//原型方法
ObjConstruct.prototype.run = function(){
return this.username + ' ' + this.age + '...';
}
//實例對象
var obj = new ObjConstruct('python',13);
alert(obj.username); //python
alert(obj.run()); //python 13...
var obj2 = new ObjConstruct('ruby',10);
alert(obj2.username); //ruby
alert(obj2.run()); //ruby 13...
alert(obj.run == obj2.run); //ture 方法共享了
可以看到,方法共享了,並且屬性可以通過構造函數在 new創建實例的時候進行傳參。如果構造函數裏也有方法呢?
原型模式的執行流程:
1.先查找構造函數實例裏的屬性或方法,如果有,立刻返回;
2.如果構造函數實例裏沒有,則去它的原型對象裏找,如果有,就返回;
此外,判斷原型,是否是某個對象的實例。都有相關的函數判斷。例如 instanceof isPrototypeOf hasOwnProperty 等。
注意 :
爲了讓屬性和方法更好的體現封裝的效果,並且減少不必要的輸入,原型的創建可以使
用字面量的方式:
function ObjConstruct(username,age){
this.username = username;
this.age = age;
}
ObjConstruct.prototype = {
constructor:ObjConstruct, //強制指向
run : function(){
return this.username + ' ' + this.age + '...';
}
}
使用構造函數創建原型對象和使用字面量創建對象在使用上基本相同,但還是有一些區
別,字面量創建的方式使用constructor屬性不會指向實例,而會指向Object,構造函數創建
的方式則相反。所以需要在原型裏,強制指向。
此外,使用字面量原型的方式,如果再在下面寫一個,將會重新原型,阻斷原型,導致前面的代碼無效。
拓展了傳遞參數的類型。比如傳一個數組。
function ObjConstruct(username,age,contect){
this.username = username;
this.age = age;
this.contect = contect
}
ObjConstruct.prototype.run = function(){
return this.username + ' ' + this.age + ' ' + this.contect;
}
var obj = new ObjConstruct('python',13,['string','list','tuple']);
alert(obj.username);
alert(obj.contect);
alert(obj.run());
6.動態模式
原型模式,不管你是否調用了原型中的共享方法,它都會初始化原型中的方法,並且在聲明一個對象時,構造函數+原型部分讓人感覺又很怪異,最好就是把構造函數和原型封裝到一起。爲了解決這個問題,我們可以使用動態原型模式。
//動態
function ObjPrototype(username,age){
this.username = username;
this.age = age;
if(typeof this.run != 'function'){
ObjPrototype.prototype.run = function(){
return this.username + ' ' + this.age + '...'
}
}
}
var obj = new ObjPrototype('pyrhon',13);
alert(obj.username); //python
alert(obj.run()); //python 13...
動態方式好處是看起來更像傳統的OOP。比如 ObjPrototype像“類”,new的obj爲對象。但是給原型加方法的時候,是不用寫出 this.prototype的。
綜上所述,理解 js 的oop。首先要明白基本類型和引用類型的差異。其次要明白 new操作符蘊含的 this指針傳遞問題。最後明白 對象的 prototype屬性和實例 __proto__屬性之間的關係。然後可以安裝正常的OOP方式構造出 “類”和實例對象。
創建對象,只是OOP中的第一步,對於其三大特性,封裝,繼承和多態。則下一次再探討。比如封裝利用作用域 閉包等手段,將私有變量,封裝成 靜態變量等。如何利用對象冒充的但是繼承 對象等技術手段。
唔 週末愉快!