咱們先來看高程三中是如何對對象進行定義的javascript
"無序屬性的集合,其屬性能夠包括基本值、對象或者函數",對象是一組沒有特定順序的的值。對象的沒個屬性或方法都有一個俄名字,每一個名字都映射到一個值。前端
簡單來理解對象就是由屬性和方法來組成的java
-封裝git
對於一些功能相同或者類似的代碼,咱們能夠放到一個函數中去,屢次用到此功能時,咱們只須要調用便可,無需屢次重寫。github
在這裏咱們能夠理解爲創造對象的幾種模式:單例模式,工廠模式,構造函數模式,原型模式等。chrome
繼承瀏覽器
子類能夠繼承父類的屬性和方法dom
多態(重載和重寫)模塊化
重載:嚴格意義上說js中沒有重載的功能,不過咱們能夠經過判斷函數的參數的不一樣來實現不一樣的功能來模擬重載。函數
重寫:子類能夠改寫父類的屬性和方法
單例模式
小王在一個小公司,就本身一個前端,因此他寫js都是這樣的
var a = 1; function getNum(){ return 1; }
後來公司又招了個前端小明,因而變成他們2個一塊兒寫同一個js了。一天小王發現本身寫的getNum方法出問題了,原來是小華寫的js中也有個getNum的函數,代碼合併後把他的覆蓋掉了,因而便找小華理論去,通過一番妥協後,兩人都把本身的代碼改了改
var xiaoming = { num:1, getNum:function(){ return 1; } } var xiaohua = { num:2, getNum: function(){ return 2; } }
這就是咱們所謂的單例模式(命名空間)
咱們把描述同一個事物的方法或者屬性放到同一個對象裏,不一樣事物之間的方法或者屬性名相同相互也不會發生衝突。
單例模式的優劣
使用單例模式,咱們能夠實現簡單的模塊化開發
var utils = { getCss:function(){ //code }, getByClass:function(){ //code }, setCss:function(){ //code } }
咱們能夠把本身寫好的工具方法放到一個單獨的js文件中,而後直接引入便可。
避免了全局變量名的衝突
須要注意的是,咱們在引入各個模塊的時候,須要注意引入的順序,引入順序是按照各模塊之間的相互依賴進行先後排列的;
缺點:
單例模式只是必定程度上避免了變量名的衝突,但並不能完全解決此問題,並且在不一樣的對象下,咱們可能會有不少功能相同的代碼,最終形成大量的冗餘代碼。
單例模式讓每一個對象有了本身獨立的命名空間,可是並不能批量生產的問題,每個新的對象都要從新寫一份如出一轍的代碼。
var person1 = { name:'小明', age:24, showName:function(){ console.log('個人名字是:'+this.name) } }; var person1 = { name:'小華', age:25, showName:function(){ console.log('個人名字是:'+this.name) } };
工廠模式
工廠模式其實就是把須要一個個的編寫的對象,放在一個函數中統一的進行建立,說白了就是普通函數的封裝。
工廠模式總共3步驟:
1)引進原材料 --- 建立一個空對象 2)加工原材料 --- 加工對象:給對象添加屬性和方法; 3)輸出產品 --- 返回對象:return 對象;
function CreatePerson(name,age){ var obj={};//1.建立一個空對象 //2.加工對象 obj.name=name; obj.age=age; obj.showName=function(){ console.log('個人名字是:'+this.name) }; return obj;//3.輸出對象; } var person1 = CreatePerson('小明',23) var person2 = CreatePerson('小華',23) person1.showName(); //個人名字是:小明 person2.showName(); //個人名字是:小華
工廠模式的優缺點
既然叫工廠模式,它就和咱們周圍的工廠同樣,咱們只須要把原材料放進去,就能獲得咱們須要的產品了。
工廠模式也解決了單例模式的批量生產的問題,避免了單例模式中的大量冗餘代碼,進行系統的封裝,提升代碼的重複利用率
不過工廠模式跟咱們js內置類的調用方法不一樣
構造函數模式
能夠建立一個自定義的類,而且能夠new出實例
構造函數作的就是類和實例打交道。
//構造函數:首字母大寫(約定俗成); function CreatePerson(name,age){ //建立一個自定義的類 //構造函數中的this,都是new出來的實例 //構造函數中存放的都是私有的屬性和方法; this.name=name; this.age=age; this.showName=function(){ console.log('個人名字是:'+this.name) } } //實例1 var person1 = new CreatePerson('小明',25) //實例2 var person2 = new CreatePerson('小華',24)
這裏說一下工廠模式和構造函數模式的區別:
1. 在調用的時候不一樣: 工廠模式:調用的時候,只是普通函數的調用createPerson(); 構造函數模式:new CreatePerson(); 2. 在函數體內不一樣: 工廠模式有三步:1)建立對象 2)加工對象 3)返回對象; 構造函數模式只有1步: 只有加工對象; 由於系統默認會爲其建立對象和返回對象; 3. 構造函數默認給咱們返回了一個對象,若是咱們非要本身手動返回的話: (1)手動返回的是字符串類型:對之前實例上的屬性和方法沒有影響; (2)手動返回的是引用數據類型:之前實例身上的屬性和方法就被覆蓋了;實例沒法調用屬性和方法;
構造函數的方法都是私有方法,每一個實例調用的都是本身私有的方法,一樣也會有許多重複的代碼。
咱們可使用原型模式來解決每一個實例中都有相同方法的函數的問題
原型模式
function CreatePerson(name,age){ this.name=name; this.age=age; } // 咱們把公有的方法放到函數的原型鏈上 CreatePerson.prototype.showName = function(){ console.log('個人名字是:'+this.name) } var person1 = new CreatePerson('小明',25) var person2 = new CreatePerson('小華',24) person1.showName() //小明
原型模式的關鍵:
1)每一個函數數據類型(普通函數,類)上,都有一個屬性,叫prototype。 2)prototype這個對象上,天生自帶一個屬性,叫constructor:指向當前這個類; 3)每一個對象數據類型(普通對象,prototype,實例)上都有一個屬性, 叫作__proto__:指向當前實例所屬類的原型;
這3句話理解了,下邊的東西就能夠不用看了 //手動滑稽
經過例子咱們來看這幾句話是什麼意思
function CreatePerson(name,age){ this.name=name; this.age=age } CreatePerson.prototype.showName=function(){ console.log('個人名字是:'+this.name) } var person1 = new CreatePerson('小明',25); console.dir(person1)
在chrome瀏覽器控制檯中顯示
從圖中能夠看出,person1這個對象上有name和age兩個屬性,
person1的__proto__指向了它的構造函數(CreatePerson)的prototype上,
並且還有一個showName的方法。 而且它們中有一條鏈關聯着: person1.__proto__ ===
CreatePerson.prototype
接着來看
function Foo(){ this.a=1; } Foo.prototype.a=2; Foo.prototype.b=3; var f1 = new Foo; //沒有參數的話括號能夠省略 console.log(f1.a) //1 console.log(f1.b) // 3 以這個爲例, 當咱們查找f1.a時,由於f1中有這個屬性,因此咱們得出 f1.a=1; 當咱們查找f1.b時,f1中沒有這個屬性,咱們便順着f1.__proto__這條鏈去 它的構造器的prototype上找,因此咱們得出了 f1.b = 3;
接着來講,Foo.prototype是個對象,那麼它的__proto__指向哪裏呢
還記的剛剛說的那句
每一個對象數據類型(普通對象,prototype,實例)上都有一個屬性,叫作__proto__:指向當前實例所屬類的原型
此外,咱們應該知道
每個對象都是function Object這個構造函數的實例
因此咱們能夠接着還原這個原型圖
等等,圖上貌似多了個個Object.prototype.__proto__ 指向了null,這是什麼鬼?
咱們這麼來理解,Object.prototype是個對象, 那麼它的__proto__指向了它的構造函數的prototype上,
最後發現了仍是指向它自身,這樣轉了個圈貌似是無心義的,因而便指向了null
還沒完,咱們發現對象都是函數(構造器)創造出來的,那麼函數是誰創造的呢?石頭裏蹦出來的麼?
在js中,function都是由function Function這個構造器創造的,每個函數都是Function的實例
如今基本上咱們就能得出了完整的原型圖了
是否是有點亂?根據咱們剛剛講的是能把這個圖理順的,
這裏須要注意下,Function.__proto__是指向它的prototype的
多說一點,判斷數據類型的方法時,咱們知道有個instanceof的方法
好比
A instanceof B
instanceof判斷的規則就是:
沿着A的__proto__這條線查找的同時沿着B的prototype這條線來找,若是兩條線能找到同一個引用(對象),那麼就返回true。若是找到終點還未重合,則返回false。
再來看咱們以前的那個例子
function Foo(){ this.a=1; } Foo.prototype.a=2; Foo.prototype.b=3; var f1 = new Foo; //沒有參數的話括號能夠省略 console.log(f1.a) //1 console.log(f1.b) // 3 當咱們查找f1.a時,由於f1中有這個屬性,因此咱們得出 f1.a=1; 當咱們查找f1.b時,f1中沒有這個屬性,咱們便順着f1.__proto__這條鏈去它的構造器的prototype上找,因此咱們得出了 f1.b = 3;
當咱們查找一個對象的屬性時,先在這個對象的私有空間內查找,若是沒找到,就順着對象的__proto__這條鏈去它的構造器的ptototype上查找,若是還沒找到,接着沿__proto__向上查找,直到找到Object.prototype尚未的話,這個值就爲undefined,這就是所謂的原型鏈
列舉下網頁中的一些相關的原型鏈
有興趣的同窗可自行經過瀏覽器控制檯看看咱們經常使用的方法都是在哪一個類上定義的,好比getElementsByTagName,addEventListener等等
在這裏就主要說一下組合繼承(call + 原型鏈)
function Father(){ this.xxx= 80; this.yyy= 100; this.drink = function(){} } Father.prototype.zzz= function(){} var father = new Father; function Son(){ this.aaa = 120; this.singing = function(){} Father.call(this); } Son.prototype = new Father; Son.prototype.constructor = Son; var son = new Son console.dir(son)
這麼寫有個很差的地方就是:子類私有的屬性中有父類私有的屬性,子類公有的屬性中也有父類私有的屬性;
根據咱們前邊的知識,咱們能夠這麼來改寫
function Father(){ this.xxx= 80; this.yyy= 100; this.drink = function(){} } Father.prototype.zzz= function(){} var father = new Father; function Son(){ this.aaa = 120; this.singing = function(){} Father.call(this); } Son.prototype.__proto__ = Father.prototype var son = new Son console.dir(son)
最後來一張思惟導圖
圖片若是放大也看不清的話 下載地址
若有錯誤,歡迎指正!