python入門python的基本語法
675
2025-03-31
目的
在對象之間建立關系和共享代碼的方法,擴展和改進既有的對象的方法。
概念
javaScript不是基于類的面向對象系統(即用類來產生對象,javaScript根本沒有類),而是基于原型模型,對象可繼承和擴展其他對象(即原型對象)的屬性和行為,這種方式我們稱之為原型式繼承或基于原型的繼承,其中其行為和屬性被繼承的對象稱為原型。這樣做的目的在于繼承既有的屬性和方法,同時在對象中添加屬性和方法。
對象字面量適合創建少量對象的情況。
對象構造函數適合創建大量一致的對象,在代碼上來看,實現了代碼重用,但在運行效率上看,創建出來的對象都會產生一個包含屬性和方法的副本,而方法的副本是完全沒有必要存在這么多的,因此會占用大量內存,影響程序性能。
將對象們共用的方法和屬性放到原型中,然后所有對象都基于此原型來創建,就能實現代碼共享。此時原型對象只有一個,不會產生不必要的對象副本。既節省了大量計算機資源,又提高了程序性能。
使用原型創建對象
在開始前,我們要思考好哪些方法是需要放到原型中去共享,哪些方法和屬性需要放在對象實例中。
一般,我們將所有對象都需要的方法放到原型中,把對象實例自身特有的屬性和方法放到實例對象中。
我們舉個利用汽車對象原型創建汽車對象實例的例子。經分析,所有汽車對象都會有牌子(brand)、控制啟動參數(started),還有啟動車子(start)、停車(stop)、行駛(drive)等方法,它包含了每個汽車對象都需要的屬性和方法。
實際上,每個對象的屬性都可能會變化,不太應該放在原型中,但我們暫時這樣,順便可以講點別的知識。分析后,汽車原型應該是這樣的:
接下來,基于這個原型創建貨車對象,而貨車對象一般都會有weight(載重),height(高),goods(貨物)這些屬性和卸貨(unload)這個方法。所以我們的貨車對象看起來是這樣的:
分析完畢。
一般來說我們應該先建原型,再建對象。但在JavaScript中,要**(1)先創建貨車對象的構造函數**,然后**(2)通過函數的屬性prototype獲得原型對象,然后往原型對象里添加屬性和方法**。步驟如下:
第一步,定義貨車對象構造函數:
function CarModel(weight,height,goods){ this.weight = weight; this.height = height; this.goods = goods; this.unload = function(){ alert("開始卸貨"); }; }
1
2
3
4
5
6
7
8
9
第二步,創建構造函數后,獲取汽車原型對象,并設置原型:
CarModel.prototype.brand = "BMW"; CarModel.prototype.started = false; CarModel.prototype.drive = function(){ //if(CarModel.prototype.started){ if(this.started){//如果實例中沒有此屬性,就會到原型中找 alert("start start start"); }else{ alert("no!"); } }; CarModel.prototype.stop = function(){ //CarModel.prototype.started = false; this.started = false;//這將在對象實例中創建屬性started }; CarModel.prototype.start = function(){ //CarModel.prototype.started = true; this.started = true;//這將在對象實例中創建屬性started };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
上述例子中之所以不用CarModel.prototype.started而用this.started是因為修改原型中的started以影響到所有對象( 再次說明, 原型中不太應該放屬性)。執行this.started = true后,會在對象實例中添加started屬性:
小知識:
在JavaScript中,函數也是對象,也有屬性。對象構造函數里包含屬性prototype,這是一個指向原型對象的引用。但是這個原型對象默認包含的屬性與方法不多,所以我們要給原型對象添加屬性和方法,這通常是在使用構造函數前進行的。
可以通過 CarModel.prototype訪問原型對象, 并通過其向原型對象中添加屬性和方法 。向原型添加的方法和屬性將被所有對象所共用。對象自己特有的方法與屬性,則在對象構造函數中添加(這其實也是在對象實例上添加),或者直接在對象實例上添加,如:
var c1 = new CarModel(11,11,"apple1"); c1.seats = 6; c1.flash = function(){ alert("turn on the flash light"); };
1
2
3
4
5
如上面的seats屬性與flash方法只屬于對象cm。其他用對象構造函數創建出的對象是沒有的。
第三步,測試:
//c1 c2 c3將會共用原型中的代碼 var c1 = new CarModel(11,11,"apple1"); var c2 = new CarModel(22,22,"apple2"); var c3 = new CarModel(33,33,"apple3"); c1.start(); c1.drive();//start start start c1.stop(); c1.drive();//no! c1.brand = "MMMM"; //這里的brand已經不是原型中那個brand了,這是我們在c1對象實例創建的brand屬性。此語句就是正在創建對象實例變量brand。 alert(c1.hasOwnProperty("brand")); //true,證實c1.brand = "MMMM"賦值語句讓brand變成c1對象實例的屬性。 alert(c1.brand);//MMMM alert(c2.brand);//BMW alert(c2.hasOwnProperty("brand"));//false,說明brand屬性來自原型,因為上一條測試語句能訪問brand,且此測試語句又說明brand不是c2對象實例的屬性,那它只能是來自c2對象的原型。
1
2
3
4
5
6
7
8
9
10
11
12
13
到這里為止,我們展示了搭建原型和通過原型創建對象的過程。
注意:原型中的屬性與方法都是共用的,沒有副本。
繼承原型并不意味著必須與它完全相同。在任何情況下,都可以重寫原型的屬性和方法,為此只需在對象實例中提供它們即可,重寫原型中的start方法:
c1.start = function(){ alert("hello Earth"); };
1
2
3
4
最后給出上述的完整代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
以上的貨車對象是在汽車原型的基礎上創建的。
建立原型鏈
對像不僅可能繼承一個原型,還可以繼承一個原型鏈。就像B原型繼承A原型,C原型繼承B原型,D原型又繼承C原型,這樣形成的一條鏈條,例如D就同時擁有A、B、C等原型的屬性與方法。
我們通過一個基于噴水車原型創建噴水車對象實例的例子來說明。提醒一下,前面我們已經有了一個汽車原型了。
分析:(1)我們決不能通過修改上面貨車構造函數CarModel來適應我們的變化,因為這一改就會影響到其他貨車對象,這硬生生給貨車加上噴水車的屬性和方法,顯然也不合理。(2)單獨再創建一個噴水車構造函數的話,那么汽車原型的代碼就要在噴水車原型中重新設置。
所以最好的做法是建個噴水車原型,然后再讓其繼承汽車原型,這樣汽車原型這部分代碼就不用重新在噴水車原型中設置。
**我們在創建汽車原型時,只需直接通過構造函數CarModel的屬性prototype獲取原型對象,然后在其中添加要讓每個汽車對象都繼承的屬性和方法即可。**在這里我們需要的是一個繼承汽車原型的噴水車原型對象。為此,我們必須創建一個繼承汽車原型的汽車對象,因為假如不創建的話,那么汽車原型對象就不會存在,再親自動手建立關聯。
噴水車原型如下:
創建原型鏈的步驟如下:
第一步創建繼承了汽車原型的對象:
對對象實例的唯一要求就是它必須繼承了汽車原型。
var car = new CarModel();
1
第二步創建噴水車對象構造函數:
function SprayCarModel(weight,height,goods,name,handler){ CarModel.call(this,weight,height,goods); this.name = name; this.handler = handler; }
1
2
3
4
5
說明:創建繼承另一個原型的構造函數時,都不應該重復既有的代碼,下面就重復了貨車對象構造函數的代碼:
function SprayCarModel(weight,height,goods,name,handler){ this.weight = weight; this.height = height; this.goods = goods; this.name = name; this.handler = handler; };
1
2
3
4
5
6
7
解決辦法:
CarModel.call(this,weight,height,goods);
1
它其實是調用CarModel對象構造函數,給當前new SprayCarModel出來的對象this賦值。傳當前對象的引用this過去,再調用CarModel對象構造函數對this進行賦值。
為什么要這樣做?
通過第三步我們可知,貨車對象實例將變成噴水車原型,而原型對象是共用的,所以將weight、height、goods放在噴水車對象實例中會更好,如果使用原型中的話,那么對象之間就會互相影響。共用原型中的方法就不會有這種問題,因為大家都是相同的,但是數據就不是了,各有各的不同。
所以這一條call語句相當于做了以下事情:
this.weight = weight; this.height = height; this.goods = goods;
1
2
3
調用對象構造函數是不會產生新對象的,和調用普通函數一樣,調用對象構造函數一般都是給對象屬性賦值。
只有用運算符new,才會產生新對象,它會先創建一個空對象并將引用賦給this,返回this,然后再調用對象構造函數對對象this進行賦值。由此可見new才會產生新對象,而調用對象構造函數是不會產生新對象的。
所以call一番操作后,噴水車原型中的屬性,就根本沒有給它們賦值過,因此它們都是未定義的undefined
第三步將新建的繼承了汽車原型的對象變成噴水車原型:
SprayCarModel.prototype = car;//將實例car變成SprayCarModel的原型
1
注意:另忘了,噴水車原型依然是一個貨車對象實例。其實,還可以通過創建一個對象字面量,然后用作原型,如
var d = {
start:function(){},
…
};
SprayCarModel.prototype = d;//SprayCarModel就將繼承d中的屬性和方法。
第四步向噴水車原型中添加屬性和方法:
SprayCarModel.prototype.volume = 11;//設置原型的屬性 //設置原型的方法 SprayCarModel.prototype.sprayWater = function(){ alert("spray spray spray"); };
1
2
3
4
5
第五步,測試:
//創建一個噴水車對象實例 var sprayCar = new SprayCarModel(29999,3,"applepie","AAP",15); alert(sprayCar.hasOwnProperty("weight"));//false alert(sprayCar.weight);//29999,上面說明weight不是實例的屬性,本語句說明能訪問weight屬性,說明weight是在原型中的。 var sprayCar1 = new SprayCarModel(999,3,"applepie","AAP",15); alert(sprayCar1.weight);//999 alert(sprayCar.weight);//29999 alert("weight belongs to sprayCar:"+sprayCar.hasOwnProperty("weight"));//true,說明通過CarModel.call(this,weight,height,goods);已將weight變成了sprayCar對象實例的屬性了。height、goods也是如此。 sprayCar.sprayWater();//訪問噴水車原型中的sprayWater方法 alert(sprayCar.volumn);//訪問噴水車原型中的屬性 alert(sprayCar.hasOwnProperty("volumn")); //false,結合上一條語句,說明volumn是在原型中的 alert(sprayCar.brand);//BMW,能訪問汽車原型中的屬性 sprayCar.drive();//能訪問汽車原型中的方法 alert(sprayCar.hasOwnProperty("name"));//噴水車對象實例的屬性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
至此,原型鏈也講完了。其實就是原型對象換成了基于上一個原型對象創建的對象實例。
下面給出完整的代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
原型鏈中的繼承原理
對象調用方法或訪問屬性時,首先在對象實例里查找,如果查找不到,就會沿繼承鏈上移,在其原型中接著查找。
意外!意外!意外!
console.log("SprayCarModel constructor is:"+sprayCar.constructor);
1
輸出的結果是:
SprayCarModel constructor is:function CarModel(weight,height,goods){ this.weight = weight; this.height = height; this.goods = goods; } ```
1
2
3
4
5
6
不可能呀,SprayCarModel才是sprayCar對象實例的構造器呀,怎么成了CarModel。原來,我們要顯式地把對象構造函數的constructor屬性設置為SprayCarModel對象構造器函數。雖然不設置也不會有什么影響,但是最佳實踐建議還是設置的好。
顯式設置對象構造函數的constructor屬性設置為SprayCarModel
SprayCarModel.prototype.constructor = SprayCarModel;
1
再看看結果:
console.log("SprayCarModel constructor is:"+sprayCar.constructor);
1
輸出的結果是:
SprayCarModel constructor is:function SprayCarModel(name,handler){ this.name = name; this.handler = handler; }
1
2
3
4
這下終于正確了。
總結:
我們創建的每個原型鏈的終點都是Object。我們創建的任何對象,默認原型都是Object,除非你對其進行了修改。噴水車原型從汽車原型派生出來,汽車原型是從Object派生出來。所有對象都是從Object派生出來的,所以我們創建的每個對象都有原型,該原型默認是Object。當然,你可以將對象的原型設置為其他對象,如噴水車原型是汽車對象實例,無論怎樣,所有原型鏈的終點都是Object。
原型是動態的,只要在原型上作任何修改,就會馬上反映到各個對象上去。可以對象實例中重寫原型中的方法和屬性。
Object實現了很多重要的方法,如hasOwnProperty、toString,它們是javaScript對象系統的核心部分。
我們常常會重寫Object原型中的toString方法,如:
TruckModel.prototype.toString = function(){ alert("HDDDDDDDD"); }; var truck = new TruckModel("baobao",true,5000,1.5,"apple"); truck.toString();
1
2
3
4
5
但不是每個方法都能重寫,如以下這些就是不能重寫的:
constructor 表示與原型相關聯的構造函數
hasOwnProperty判斷實例是否有此屬性,每個對象都有此方法。如果屬性不是在對象實例中定義的,但能夠訪問它,就可以認為它肯定是在原型中定義的。
isPrototypeOf判斷一個對象是否是另一個對象的原型
如car.isPrototypeOf(truck) //true
propertyIsEnumerable用于判斷通過迭代對象的所有屬性是否可訪問指定的屬性。
而下面這些方法是可重寫:
toString
toLocaleString
valueOf
給一個運行實例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
擴展內置對象
其實與上面的一樣,通過在原型中添加方法和屬性,如擴展String內置對象:
String.prototype.clickme = function(){ alert("you clicked me"); };
1
2
3
其他的依次類推。
最后回顧一下:
JavaScript對象系統使用原型式繼承
使用構造函數創建對象實例時,實例包含自己的自定義屬性,還有構造函數中方法的副本。
給構造函數的原型添加屬性后,使用這個構造函數創建的實例都將繼承這些屬性。
通過在原型是中定義屬性,可減少對象包含的重復代碼。
要重寫原型中的屬性,只需在實例中添加該屬性即可。
構造函數有默認的原型,可通過函數的屬性prototype來訪問它。
可將你自己創建的對象賦給構造函數的屬性prototype
使用自定義的原型對象時,務必將原型的屬性constructor設置為相應的對象構造函數,以保持一致。
給原型添加屬性后,繼承該原型的所有實例都將立即繼承這些屬性,即便是以前創建的實例也不例外。
歸根結底,所有原型和對象都是從Object派生而來的。
Object包含所有對象都將繼承的屬性和方法,如toString和hasOwnProperty
可給內置對象(如Object和String等)添加屬性,也可重寫它們的既有屬性,但要小心。
在JavaScript中,一切幾乎皆是對象,包括函數、數組和眾多的內置對象和自己創建的自定義對象。
謝謝閱讀。
AI JavaScript 交通智能體
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。