js面向對象,繼承

很開心,最近收穫了很多知識,而且發現很多東西,以前理解的都是錯的,或者是膚淺的,還以爲自己真的就get到了精髓,也很抱歉會影響一些人往錯誤的道路上走,不過這也告訴了我們,看任何一篇文章都不能盲目的去相信,要實踐驗證再驗證。今天就重新整理一下,我對面向對象的理解,當然也不保證完全正確的,但絕對是在進步的,拋磚引玉,希望能帶來一些新的感悟。

 對象,通俗的來說,就是屬性和方法。定義就不再多說,下面說對象的創建:
1 創建一個面向對象

複製代碼
var obj = new Object(); //創建一個空對象
obj.name = 'haha';
obj.showName = function(){ 
   alert(obj.name);
}
obj.showName();
複製代碼
缺點:當我們想創建多個面向對象的時候,重複代碼過多,需要封裝,所以有了下面的方法

2 工廠方式

複製代碼
function CreatePerson(name){ 
    //原料
   var obj = new Object();
    //加工
   obj.name = name;
   obj.showName = function(){ 
       alert(this.name);
   }
   //出廠
   return obj;
}

var p1 = CreatePerson('haha');
p1.showName();
var p2 = CreatePerson('hehe');
p2.showName();
複製代碼

這其實就是簡單的封裝函數,整個過程像工廠的流水線,所以叫工廠方式

缺點:無法識別創建的對象的類型。因爲全部都是Object,沒有區分度,不像Date、Array等,因此出現了構造函數模式。

3 構造函數模式

我們要通過這二個方面來改變:1 函數名首字母大寫 2 New 關鍵字調用

複製代碼
function CreatePerson(name){ 
     this.name = name; 
     this.showName = function(){ 
        alert(this.name); 
     } 
} 
var p1 =new CreatePerson('haha'); 
 p1.showName();
var p2 = new CreatePerson('hehe');
 p2.showName();

複製代碼
1首字母大寫,是爲了區別於普通的函數,構造函數本身就是普通的函數,只是我們專門用它來實現了構造的功能,所以專門起了一個名字叫構造函數,任何函數都可以成爲構造函數,這取決於你調用函數的方式。是否用了New。

2 調用函數的時候用了 New關鍵字,那麼New到底做了什麼?用不用New有什麼區別?再來看下面的例子

` ` 
複製代碼
function CreatePerson(name){   
  this.name = name; 
  this.showName = function(){ 
    alert(this.name); 
  };
  console.log(this);
} 

new CreatePerson('haha'); //CreatePerson{}
CreatePerson('haha');  //window
複製代碼


我們會發現當用New去調用一個函數的時候,this的指向會不一樣。其實New主要做了下面這些事,不過下面寫的只是大概的行爲,並不是內部源碼。

複製代碼
function CreatePerson(name){   
  var res = {};  //聲明一個空對象res
  res._proto_= CreatePerson.prototype;//這個對象的原型鏈指向傳入的函數的原型,這樣res就可以調用CreatePerson原型下的所有方法
  CreatePerson.apply(res);//把this指向改爲res對象
  this.name = name;  //res對象添加屬性,方法
  this.showName = function(){ 
    alert(this.name); 
  };
  return res;//返回這個對象
} 

複製代碼
關於New做時候都是內部的行爲,看不到但確實存在,關於上面原型可以先大概知道結論,下面會說原型,接着看就懂了。

函數構造模式存在的問題:

alert(p1.showName==p2.showName);//false

測試這個代碼,兩個方法是不相同的,也就是說這兩個對象並不是共用一個方法,每new一次,系統都會新創建一個內存,這兩個對象各自有各自的地盤,但他們具有相同的功能,還不共用,肯定不是我們所希望的。所以就有了下一種方法,原型模式

4 原型+構造模式

每個函數都有一個prototype屬性,它是一個對象,也稱作原型對象,這個原型對象,我們可以把方法和屬性寫在它上面(不過原型對象不僅僅有我們寫的屬性和方法,還有別的,下面會介紹),而通過這個函數創建出來的實例對象,都能共享這個對象下的方法和屬性。所以我們只需要把想要共享的東西放在函數的prototype下,不想共享的東西通過構造函數來創建就可以了。

看個栗子(原型+構造)

複製代碼
function CreatePerson(name){ 
    this.name = name;
}
CreatePerson.prototype.showName = function(){ 
    alert(this.name);
}
var p1 =new CreatePerson('haha');
p1.showName();
var p2 = new CreatePerson('hehe');
p2.showName();

alert(p1.showName==p2.showName);//true
複製代碼
通過最後一句的測試爲true,可以看到在構造函數的原型下面加的方法showName()方法是所有通過這個構造函數創建出來的對象所共享的,也就是說他們共用一個內存,更進一步的說它們存在引用關係,也就是說你更改了p1的showName也會影響p2的showName。

所以我們在構造對象的時候,一般是原型模式和構造模式組合使用,變化的用構造模式 不變的公用的用原型模式,就像上面的這個栗子,屬性用的構造函數,因爲一般不同對象屬性都不同,方法用原型模式。

_proto_屬性:上面我們只是說了,同一個函數造出來的實例對象能共享這個函數的prototype下的方法和屬性,但是它是如何做到的呢?這裏要出場的就是_proto_屬性,每個實例化對象都有一個_proto_屬性,它是一個指針,指向函數的prototype,也就是保存了它的地址。(JS中任何對象的值都是保存在堆內存中,我們聲明的變量只是保存了這個對象的實際地址,所以有了地址就能找到對象),所以總得來說,每個實例化對象都有_proto_屬性,保存了構造函數的原型對象的地址,通過這個屬性就可以擁有原型對象下的所有屬性和方法。_proto_屬性實際就是實例化對象和原型對象直接的連接。

原型鏈: 每個函數都可以成爲構造函數,每個函數都有原型對象,每個原型對象也可以是一個實例化對象,比如,你創建了一個函數fun,它是構造函數function的實例化對象,而function的原型對象,又是Object的實例對象。所以fun有個_proto_屬性可以訪問到function的原型對象,function原型對象也是個實例對象,也有個_proto_屬性,可以訪問到Object的原型對象,所以通過_proto_屬性,就形成了一條原型鏈。每個實例化對象都可以訪問到鏈子上方的方法和屬性,所以fun是可以訪問Object原型對象下的方法和屬性的。實際上所有對象都可以訪問到Object的原型對象。

原型鏈的訪問規則:先在自身的下面尋找,再去一級一級的往原型鏈上找。如下:

function Aaa(){}
Aaa.prototype.num = 3;
var a1 = new Aaa();
a1.num =10;
alert(a1.num); //10

原型對象下的方法和屬性:原型對象下面可能有三大類:1 原型對象所帶方法和屬性 2 constructor 3 proto

constructor:構造函數屬性,每個函數的原型對象都有的默認屬性,指向函數。每個實例化對象本身是沒有constructor屬性的,每個實例化對象下面都默認只有一個proto,用來連接原型對象,而和構造函數本身是沒有直接的聯繫的。所以它的constructor是訪問的原型對象上的。所以當原型對象的constructor變化了,實例化對象的constructor也會改變。但是如果這個對象本身既是原型對象,又是實例化對象,那就擁有了constructor屬性,無需從原型對象繼承。

看下面的例子,來驗證我們所說的:

複製代碼
function CreatePerson(name){
   this.name = name;
}
CreatePerson.prototype.showName = function(){
  console.log(this.name);
};
var p1 =new CreatePerson('haha');
p1.showName();
console.log(p1.constructor);  // CreatePerson   來自CreatePerson.prototype

console.log(CreatePerson.prototype); //  {showName:function(){},constructor:CreatePerson,__proto__:Object.prototype}
//可見,原型對象保存了1  自身添加的方法,2 構造函數constructor  3 _proto_(和上一層構造函數原型對象的連接)
console.log(CreatePerson.prototype.__proto__===Object.prototype);//  true  這個原型對象本身又是object的實例化對象,所有_proto_指向Object的原型對象
console.log(CreatePerson.prototype.__proto__===Object);// false     可見是和構造函數下原型對象的連接,不是構造函數
console.log(CreatePerson.prototype.constructor);//CreatePerson      默認有的 指向創造它的函數

console.log(Object.prototype.__proto__); // null     原型鏈的終點是null

console.log(CreatePerson.__proto__);   //function.prototype   CreatePerson本身既是構造函數又是function的實例化對象,擁有_proto_屬性,指向function的原型對象
console.log(CreatePerson.constructor); // function    繼承自function.prototype


console.log(CreatePerson.prototype instanceof CreatePerson ) //驗證是否在一條原型鏈上  false
複製代碼

字面量法定義原型:

爲了創建對象的代碼更方便,你一定見過這樣的代碼,就是字面量法:

複製代碼

function Aaa(){}
Aaa.prototype = {
    showName:function(){alert(10);}
};
 var a1 = new Aaa();
console.log(Aaa.prototype);//{showName:function(){},_proto_}   你會發現constructor不見了,因爲這種方式相當於重新賦值了Aaa.prototype 

console.log(Aaa.prototype.constructor);//Object  因爲自身沒有了constructor屬性,就去上級原型對象找,找到了Object
console.log(a1.constructor );//Object 也變了,驗證了它是訪問的原型對象上的

複製代碼
因此我們在寫的時候需要修正一下原型的指向:
複製代碼

function Aaa(){}
Aaa.prototype = {
  constructor:Aaa,
  num1:function(){alert(10);}
} 
var a1 = new Aaa(); 
a1.constructor // Aaa

複製代碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章