Javascript繼承的最佳實務,javascript最佳實務
什麼是繼承?
繼承是物件導向最顯著的一個特性。繼承是從已有的類中派生出新的類,新的類能吸收已有類的資料屬性和行為,並能擴充新的能力。
在Javascript 中 沒有 類的概念, 它是通過建構函式來產生 對象,
建構函式 就是一個普通的函數,通常當函數名 為 大寫開頭的,我們認為是建構函式,否則 就是普通的方法。
function A() { this.name = 'A Class instance';}function m1() {}
既然 Javascript 是 通過建構函式來產生 對象,那我們怎麼定義它的 屬性、方法呢?
var a1 = new A(); 是新建立一個A類對象,預設情況下,在建構函式中,使用this指向的是 新建立的對象;
而Javascript的對象屬性 可以 晚綁定,即
var obj = {};obj.name = 'obj1';obj.say = function say() { console.log(this.name);}可以先產生對象,在需要增加屬性時, 通過 obj.屬性名稱 或 obj['屬性名稱'] 來添加屬性。
所以我們在建構函式中 使用 this.屬性名稱 來 定義產生的 對象 的 屬性和方法。
function A() { this.name = 'A Class instance'; this.say = function() { console.log('Hi,I am ' + this.name); }}var a1 = new A();var a2 = new A();a1.say(); // Hi,I am A Class instancea2.say(); // Hi,I am A Class instance
我們分析一下上面代碼,
建立了 a1,a2 對象,在建立a1,a2 對象的時候 都為其添加一個say()方法,而這2個對象的 say方法 完全一樣,
試想想 如果 建立n 個 A類對象,那是不是為這n個對象 添加一個say()方法,那是非常浪費記憶體。
所以在Javascript 中 引用了 prototype 原型的概念:
每一個建構函式都有一個prototype 對象,使用建構函式執行個體化一個對象,訪問這個對象屬性時,如果這個對象有該屬性,則返回,否則就會在該對象的建構函式的prototype 上找,直到找到就返回,否則返回的是undefined
來驗證一下:
在上面代碼中,A建構函式中沒有定義say 方法,
但a1,a2 卻 能夠 調用say()方法,因為 A函數的prototype 預設指向的是 Object.prototype ;
此時記憶體中只儲存Object.prototype.say,而A產生的對象 是通過原型機制,一層一層往上找,然後調用的。
所以通過 prototype原型機制,我們可以實現代碼複用,和繼承。
A.prototype.say = function() { console.log('Hi,I am ' + this.name);}function A() { this.name = 'A Class instance';}var a1 = new A();var a2 = new A();a1.say(); // Hi,I am A Class instancea2.say(); // Hi,I am A Class instancefunction B() { this.name = 'B Class instance';}B.prototype = new A();B.prototype.constructor = B;var b = new B();b.say();
分析一下上面代碼 執行結果:
我們定義了一個B類,並把他的prototype 指向 A的執行個體對象,
然後產生一個 b 對象,調用b對象 say() ,也輸出了內容。
這是為什麼呢?
訪問 b 對象屬性時, 如果不存在 ,就會在其 prototype 訪問,
就是 訪問a 對象的 prototype ,但a對象 prototype 預設 是 指向Object.prototype 的,
而我們在 Object.prototype 定義了say 方法,b 對象也能訪問 say()方法, 就好像b 繼承了 父類中的 屬性一樣。
這裡我們也可以看出,一旦 原型鏈 過長,會導致 訪問多個對象的prototype。
所以 在設計 的時候 應該 不超過 3層原型鏈,可以考慮其他方式。
Javascript 最佳的實踐:
A.prototype.say = function() { console.log('Hi,I am ' + this.name);}function A() { this.name = 'A Class instance';}通過原型對象,添加方法,
通過建構函式 定義 對象的 屬性。
繼承還可以有很多種 實現方式, 比如 屬性的複製等等,應當靈活運用。
javascript繼承問題
呵呵 你的繼承還得學習下!! 給你最經典的一句:
代碼如下:
function WhitePerson (name){
this.name = name;
Person.call(this,name);
if(typeof WhitePerson._initialized=="undefined"){
WhitePerson.prototype.area = function (){
alert("中國"+this.name);
};
WhitePerson._initialized =true;
}
}
WhitePerson.prototype = new Person ();
好好想想為什麼吧!!
javascript繼承有哪兩種形式
Javascript的繼承在很多書裡面細緻的分了很多種類型和實現方式,大體上就是兩種:對象冒充、原型方式。這兩種方式各有優點和缺陷,這裡我給你先列舉出來,再從底層分析區別:
(一)對象冒充
function A(name){
this.name = name;
this.sayHello = function(){alert(this.name+” say Hello!”);};
}
function B(name,id){
this.temp = A;
this.temp(name); //相當於new A();
delete this.temp; //防止在以後通過temp引用覆蓋超類A的屬性和方法
this.id = id;
this.checkId = function(ID){alert(this.id==ID)};
}
當構造對象B的時候,調用temp相當於啟動A的建構函式,注意這裡的上下文環境中的this對象是B的執行個體,所以在執行A建構函式指令碼時,所有A的變數 和方法都會賦值給this所指的對象,即B的執行個體,這樣子就達到B繼承了A的屬性方法的目的。之後刪除臨時引用temp,是防止維護B中對A的類對象(注 意不是執行個體對象)的引用更改,因為更改temp會直接導致類A(注意不是類A的對象)結構的變化。
我們看到了,在Js版本更新的過程中,為了更方便的執行這種上下文this的切換以達到繼承或者更加廣義的目的,增加了call和apply函數。它們的 原理是一樣的,只是參數不同的版本罷了(一個可變任意參數,一個必須傳入數組作為參數集合)。這裡就以call為例子,解釋一下用call實現的對象冒充 繼承。
function Rect(width, height){
this.width = width;
this.height = height;
this.area = function(){return this.width*this.height;};
}
function myRect(width, height, name){
Rect .call(this,width,height);
this.name = name;
this.show = function(){
alert(this.name+” with area:”+this.area());
}
}
關於Call方法,官方解釋:調用一個對象的一個方法,以另一個對象替換當前對象。
call (thisOb,arg1, arg2…)
這也是一種對象冒充的繼承,其實在call方法調用的時候發生的事情也是上下文環境變數this的替換,在myRect函數體中this肯定是指向類 myRect對象的執行個體了,然而用這個this作為上下文環境變數調用名字叫Rect方法,即類Rect的建構函式。於是此時調用Rect時候對this 的賦值屬性和方法都實際上是對一個myRect的對象進行。所以說儘管call和apply並不是僅僅為了繼承而新增的方法,但用它們可以類比繼承。
對象冒充繼承就是這麼一回事,它可以實現多重繼承,只要重複做這一套賦值的流程就可以了。不過目前真正大規模使用得並不多,為什麼呢?因為它有一個明顯的 效能缺陷,這就要說道OO的概念了,我們說對象是成員+成員方法的集合,構造對象執行個體的時候,這些執行個體只需要擁有各自的成員變數就可以了,成......餘下全文>>