標籤:script 使用 佔用 ret 缺點 struct 基本類型 意思 1.3
一、封裝
原文連結:http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_encapsulation.html
1.1 原始模型
var Cat = {//原型 name : "",
color :""}
var cat1 = {};//執行個體
cat1.name = "小花";
cat1.color = "花";
var cat2 = {};//執行個體
cat2.name = "小黑";
cat2.name = "黑";
這就是最簡單的封裝了,把兩個屬性封裝在一個對象中,但是這樣的封裝有兩個缺點
1、如果寫多個執行個體會很麻煩
2、執行個體也原型之間沒有任何關係
1.2 原始模型的改進
寫一個函數解決代碼重複問題
function Cat(name,color){
return {
name : name,
color:color
}
}
var cat1 = Cat("小花",“花”);
var cat2 = Cat("小黑" , "黑");
存在的問題:cat1和cat2之間沒有任何內在關係,不能看出他們是同一原型的執行個體
1.3 建構函式模式
為瞭解決從原型對象產生執行個體問題,js提供一個建構函式模式。
所謂的建構函式就是普通的函數,內部使用this對象,對建構函式內部使用new運算子就可以產生執行個體,並且this變數會綁定在執行個體對象上
var Cat = function(name,color){
this.name = name;
this.color = color;
}
var cat1 = new Cat("小花",“花”);
var cat2 = new Cat("小黑",“黑”);
js提供了一個instanceof 運算子,驗證原型對象和執行個體對象之間的關係
alert( cat1 instanceof Cat);//ture
alert( cat2 instanceof Cat);//true
存在的問題:如果存在不變的屬性或方法時,比如var Cat = function(name,color){ this.name = name;
this.color = color;
this.style = "貓科"
this.eat = function(){
alert("吃老鼠");
}
}
var cat1 = new Cat("小黑",“黑”);
var cat2 = new Cat("小花",“花”) ;
alert(cat1.style);//貓科
cat1.eat();//吃老鼠
alert(cat2.style);//貓科
cat2.eat();//吃老鼠
對於每一個執行個體對象,type和eat都是一樣的內容,每一次產生一個執行個體,都必須為重複內容,多佔用一些記憶體,既不環保也缺乏效率
解決辦法:prototype模式
每一個建構函式都有一個prototype屬性,指向prototype對象,這個對象的所有屬性和方法都被建構函式的執行個體繼承
這意味著,我們可以把那些不變的屬性和方法直接定義在prototype上
var Cat = function(name,color){
this.name = name;
this.color = colr;
}
Cat.prototype = {
constructor : Cat,
style : "貓科",
eat : function(){
alert("吃老鼠");
}
}
var cat1 = new Cat("小黑",“黑”);
cat1.eat();
alert(cat1.style);
var cat2 = new Cat("小花",“花”);
alert(cat2.style);
cat2.eat();
prototype驗證方法
isPrototypeOf方法判斷,某個prototype對象和執行個體之間的關係
alert( Cat.prototype isPrototypeOf (cat1) ) ;//true
alert(Cat.prototype isPrototypeOf( cat2 ) );//true
instanceof 判斷執行個體和父類之間的關係
alert( cat1 instanceof Cat);//true
alert(cat2 instanceof Cat);//true
每一個執行個體對象都有hasOwnProperty()方法,判斷屬性是區域屬性還是繼承prototype的屬性
alert( cat1 hasOwnProperty(name) );//true 為區域屬性
alert( cat1 hasOwnProperty(eat) );//false 為繼承屬性
in 運算子用於判斷執行個體是否有某個屬性,無論是區域屬性還是繼承屬性
alert( "name" in cat1 );//true
alert("age" in cat1);//false
in還可以變數對象中的所有屬性
for( var pro in cat1){
alert("cat1的”+pro +"屬性值為:"+cat1[pro]);
}
二、繼承
方法一、建構函式的繼承
function Animal (){
this.species = "動物";
}
function Cat(name,color){
this.name = name;
this.color = color;
}
怎樣才能是貓繼承動物呢?
方法一:建構函式綁定
function Animal(){
this.species = "動物";
}
function Cat(name,color){
Animal.apply(this,arguments);
this.name = name;
this.color = color;
}
var cat1 = new Cat("小黑",“黑”);
alert( cat1.species );//“動物”
方法二:原型模式
如果Cat的prototype成為Animal的執行個體,cat就可以繼承Animal了
Cat.prototype = new Animal();Cat.prototype.constructor = Cat;var cat1 = new Cat();alert( cat1.species );//"動物"
任何一個建構函式的原型對象都有一個constructor屬性,此屬性都指向這個建構函式。
當沒有Cat.prototype = new Animal();的時候,Cat.prototype.constructor指向的是Cat,但是繼承了Animal後,Cat.prototype.constructor = Animal;
所以要進行更正,Cat.prototype.constructor = Cat;
更重要的是執行個體也有constructor屬性,指向的是建構函式原型對象的constructor屬性
alert( cat1.constructor == Cat.prototype.constructor);//true
因此當Cat.prototype = new Animal();後 cat1.constructor = Animal,cat1明明是使用建構函式Cat產生的,但此時確是Animal,導致了原型鏈的混亂,所以要手動更正constructor,
Cat.prototype.constructor = Cat
方法三:直接繼承prototype
方法三是方法二的改進:由於Animal對象中,不變的屬性都可以直接寫入Animal.prototype。所以,我們也可以讓Cat()跳過 Animal(),直接繼承Animal.prototype。function Animal(){}
Animal.prototype.species = "動物";
然後將Cat的prototype指向Animal.prototype
Cat.prototype = Animal.prototype ;
Cat.prototype.constructor = Cat;
var cat1 = new Cat("小黑",“黑”);
alert(cat1.species);//動物
與前一種方法相比,這樣做的優點是效率比較高(不用執行和建立Animal的執行個體了),比較省記憶體。缺點是 Cat.prototype和Animal.prototype現在指向了同一個對象,那麼任何對Cat.prototype的修改,都會反映到Animal.prototype。
所以,上面這一段代碼其實是有問題的。請看第二行
Cat.prototype.constructor = Cat;
這一句實際上把Animal.prototype對象的constructor屬性也改掉了!
alert(Animal.prototype.constructor); // Cat
方法四:利用Null 物件作為中介
var F = function(){};
F.prototype = Animal.prototype;
Cat.prototype = new F()
Cat.prototype.constructor = Cat;
F作為空白對象,幾乎不佔記憶體,此時修改Cat的prototype對象,就不會修改Animal的prototype對象了
將上面方法封裝成一個函數方便使用
function extend(Child,Parent){
var F = function(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
Child.uber = Parent.prototype;
}
extend(Cat,Animal);
var cat1 = new Cat();
alert( cat1.species);//動物
這個是YUI庫實現繼承的方法
另外,說明一點,函數體最後一行
Child.uber = Parent.prototype;
意思是為子物件設一個uber屬性,這個屬性直接指向父物件的prototype屬性。(uber是一個德語詞,意思是"向上"、"上一層"。)
這等於在子物件上開啟一條通道,可以直接調用父物件的方法。這一行放在這裡,只是為了實現繼承的完備性,純屬備用性質。
方法五:拷貝繼承
將父物件的所有屬性和方法都拷貝到子物件中
function Animal(){}
Anima.prototype.species = "動物";
function extend2( Child,Parent){
var p = Parent.prototype;
var c = Child,prototype;
for(var i in p){
c[i] = p[i];
}
Child.uber = p
}
extend2( Cat,Animal );
var cat1 = new Cat("小花",“花”);
alert(cat1.species);//動物
三、非建構函式的繼承
var Chines = {
nation : "中國"
};
var Doctor = {
career:"醫生";
}
讓醫生繼承中國,成為中國醫生。
方法一:object()方法
function object(parent){
var F = function(){};
F.prototype = parent;
return new F();
}
var Doctor = object(Chinese);
Doctor.career = "醫生";
alert(Doctor.nation);//中國
方法二:淺拷貝
function extend3(p){
var c = {};
for(var i in p){
c[i] = p[i];
}
c.uber = p;
return c;
}
var Doctor = extend3(Chines);
Doctor.career = "醫生";
alert(Doctor.nation);//中國
但是,這樣的拷貝有一個問題。那就是,如果父物件的屬性等於數組或另一個對象,那麼實際上,子物件獲得的只是一個記憶體位址,而不是真正拷貝,因此存在父物件被篡改的可能。
請看,現在給Chinese添加一個"出生地"屬性,它的值是一個數組。
Chinese.birthPlaces = [‘北京‘,‘上海‘,‘香港‘];
通過extendCopy()函數,Doctor繼承了Chinese。
var Doctor = extendCopy(Chinese);
然後,我們為Doctor的"出生地"添加一個城市:
Doctor.birthPlaces.push(‘廈門‘);
發生了什麼事?Chinese的"出生地"也被改掉了!
alert(Doctor.birthPlaces); //北京, 上海, 香港, 廈門
alert(Chinese.birthPlaces); //北京, 上海, 香港, 廈門
所以,extendCopy()只是拷貝基本類型的資料,我們把這種拷貝叫做"淺拷貝"。這是早期jQuery實現繼承的方式。
方法三:深拷貝
function deepCopy(parent,c){
var c = c || {};
for( var i in parent){
if( typeof(parent[i] == "object") ){
c[i] = (parent[i].constructor == Array ) ? [] : {};
deepCopy(parent[i] , c[i]);
}else{
c[i] = parent[i];
}
}
return c;
}
var Doctor = deepCopy(Chinese);
Doctor.carrer = "醫生";
Chinese.brithPlace = ["上海",“北京”,“南京”]
alert(Doctor.nation);//"中國"
Doctor.brithPlace.push ("內蒙古");
alert(Doctor.brithPlace);//["上海",“北京”,“南京”,“內蒙古”]
目前jquery庫使用的是這種繼承方法
js物件導向