JavaScript是一門極其靈活的語言,爛七八糟的設計是它最大的優勢。不一樣於其餘嚴格類型的語言例如java,學習曲線比較友好。JavaScript我的感受上手基本不用費勁,要想上高度那就是一個悲催併且毀三觀的故事。特別是有面向對象語言基礎的人來講,JavaScript真像一個噩夢。JavaScript更加的零碎,封裝的不是很好。你必須理清脈絡深刻理解了,才能寫出來高大上的優雅的代碼。在下儘可能的用簡練易懂的語言,簡單的闡述一下我對JavaScript面向對象的一點粗淺的理解。java
var object = new Object(); object.name="huazi"; object.age="22"; object.sayHi = function(){ console.log("hi"+this.name); } object.sayHi(); //hi huazi
首先經過一個名稱爲object的對象,爲其添加name和age屬性以及一個sayHi的方法。this.name將會被解析成object.name。這種方式的缺點顯而易見:使用同一個接口(Object)建立對象,產生大量的冗餘代碼。因此通常不會這麼使用。安全
function createObject(name,age){ var obj = new Object(); obj.name=name; obj.age=age; obj.sayHi=function(){ console.log("hi "+this.name); } return obj; } var obj = createObject("huazi",22); console.log(obj.sayHi(); //hi huazi
此種方式建立對象叫作工廠模式,你給我屬性,我給你個包含這些屬性和方法的對象.這種模式建立的對象也有很大的問題:獲得的對象屬於什麼類型的是不肯定的.instanceof發現只能匹配到Object,不能匹配到createObject。app
function createObject(name,age){ this.name=name; this.age=age; this.sayHi=function(){ console.log("hi "+this.name); } } var obj = new createObject("huazi",22); obj.sayHi(); //hi huazi obj instanceof createObject;//true
構造器模式建立對象的過程分爲四步:1,建立一個Object類型對象.2,將執行環境交給這個對象(this指向).3,執行構造很熟.4,返回對象.此種方式解決了類型不肯定的問題.可是缺點是,在每次建立對象的過程當中,都會從新建立類如sayHi的對象.而這明顯是沒必要要的.修改以下:函數
function createObject(name,age){ this.name=name; this.age=age; this.sayHi=sayHi; } function sayHi(){ console.log("hi "+this.name); } var obj = new createObject("huazi",22); obj.sayHi(); //hi huazi obj instanceof createObject;//true
很簡單,只須要把函數放到全局做用域便可。但是問題又來了,全局做用域中的函數只能被某一個對象調用。這在邏輯上實在有點牽強。更嚴重的狀況是,每每咱們須要定義不少方法來實現一個對象。因此就會出現大量的全局函數,而且全局函數不能在其餘對象上使用。學習
function createObject(){}; createObject.prototype.name = "huazi"; createObject.prototype.age=22; createObject.prototype.sayHi=function(){ console.log("hi "+this.name); } var obj1 = new createObject(); var obj2 = new createObject(); obj1.sayHi(); //hi huazi obj1.sayHi===obj2.sayHi; //true
好了,如今好像解決了剛纔的問題,把屬性和方法都加到了原型中。這樣就不會出現全局屬性和重複函數對象了。這種模式的缺點也顯而易見:構造不能傳參,也就是說全部對象將長的如出一轍。還有就是內存共享的問題。屬性要是引用類型好比Array那麼就熱鬧了。牽一髮動全身。this
function createObject(name,age){ this.name=name; this.age=age; this.array = [1,2,3]; } createObject.prototype.sayHi = function(){ console.log("hi "+this.name); } var obj1 = new createObject("huazi",22); var obj2 = new createObject("huazi",22); obj1.sayHi(); //hi huazi obj1.array===obj2.array;//false
除了此種寫法以外,在給原型加方法的時候還可使用字面量的方式添加。可是須要注意的是使用字面量添加等於重寫了prototype了,因此須要顯示的申明,constructor的指向。spa
createObject.prototype={ constructor:createObject, sayHi:function(){ console.log("hi "+this.name); } }
還有須要注意的一點是,在使用字面量以前不能建立對象。前面說過此種方式等因而重寫了prototype。因此以前建立的對象實例,不會更新新的屬性和方法。爲啥?自行腦補一下。prototype
function createObject(name,age){ this.name=name; this.age=age; if(typeof this.sayHi != "function"){ //不存在的狀況下添加方法 createObject.prototype.sayHi = function(){ console.log("hi "+this.name); } } } var obj1 = new createObject("huazi",22); var obj2 = new createObject("saint",22); obj1.sayHi(); //hi huazi obj2.sayHi(); //hi saint
這種方式就算比較完美的了。可是還還須要注意,在構造內部不能使用字面量的方式去添加原型屬性。回溯到構造建立對象過程,咱們知道第一步就已經建立好對象了。因此使用字面量也會發生意外的事情。設計
寄生構造模式的思路是:建立一個函數,這個函數的職責就是封裝一個對象所須要的全部屬性和方法,而後返回對象。從樣子上來看,此種方式幾乎與工廠模式無異。只是早建立對象的時候方式有些變化。就是使用new關鍵字來建立。指針
function createObject(name,age){ var obj = new Object(); obj.name=name; obj.age=age; obj.sayHi=function(){ console.log("hi "+this.name); } return obj; } var o = new createObject("huazi",22); o.sayHi();
再回憶一下構造模式建立對象的最後一步,返回對象。寄生模式實際上是覆蓋了返回對象的那一步。同時此種模式也沒有擺脫類型不能肯定的問題。那麼此種模式在何時能夠用到呢?
function createObject(){ var array = new Array(); //Array構造中建立對象是使用push的 array.push.apply(array,arguments); //添加新的方法 array.toPipeString=function(){ return this.join("|"); } return array; } var o = new createObject("huazi",22); o.toPipeString(); //huazi|22
爲Array添加了一個方法,同時沒有修改Array的構造和原型。實際上就還對Array的二次包裝,添加新的方法。這種方式比較靠譜。安全性也比較好,夠封閉。
還有一種很是安全的建立對象模式叫作穩妥模式。與寄生模式很是的相似
function createObject(name,age){ var obj = new Object(); obj.sayHi=function(){ console.log("hi "+name); } return obj; } var o = new createObject("huazi",22); o.sayHi(); //hi huazi
能夠發現,name值只能在sayHi中訪問,在構造中包裝的數據是絕對安全可靠的。這就有點private的意思了。sayHi是暴露給外部的接口。和寄生模式同樣採用穩妥模式建立的對象,類型是沒法肯定的。
不管何時只要是建立了一個新函數,隨之就會根據一組特定的規則爲該函數建立一個prototype屬性。在默認狀況下prototype屬性會自動得到一個constructor(構造函數)屬性,這個函數很是的特殊。包含一個指向prototype屬相所在函數的指針。
function createObject(name,age){} var o = new createObject("huazi",22); console.log(createObject.prototype.constructor);//function createObject(name,age) {} console.log(createObject.prototype)//createObject(name,age) {} //判斷對象o是不是原型對象的派生。 console.log(createObject.prototype.isPrototypeOf(o));//true
其實是這樣的。每一個對象都會有一個_proto_屬性,這個屬性指向的是函數原型createObject.prototype。而crateObject.prototype中存在一個constructor屬性,此屬性指向了createObject構造函數。等於指來指去,指出了一個迴路。isPrototypeOf函數的參數是函數對象實例,做用是判斷該實例的歸屬,是不是該原型對象的派生。使用hasOwnProperty()能夠檢測一個方法是存在於原型中仍是存在於實例中,固然前提是肯定能夠訪問這個方法或者這個方法存在才能肯定。使用delete操做符能夠刪除掉實例中的方法或者屬性。在原型中的屬性方法默認是不能被delete的。還有一個坑就是delete不存在的屬性或者方法也會返回true因此在使用delete的時候須要當心一些。
在給prototype添加屬性以前建立了一個對象,那麼這個對象是否能夠引用新添加的原型上的屬性呢?答案是能夠的。在訪問屬性的時候首先會查看實例對象是否存在此屬性,否則就去原型找。而_proto_就是指向原型對象的。沒錯是指向,因此無論什麼時候更新原型屬性都是ok的。
ECMAScript中描述了原型鏈的概念,並說明原型鏈將做爲實現繼承的主要方法。原型鏈顧名思義,就是講原型連接起來實現繼承。子類的prototype指向父類的實例對象就是很簡單的一種實現。
function superClass(){ this.name="huazi"; } superClass.prototype.getName=function(){ console.log(this.name); } function childClass(){ this.name="bob"; } childClass.prototype = new superClass(); childClass.prototype.getName = function(){ console.log(this.name+"v"); } var instance = new childClass(); //重寫了父類方法和屬性 instance.getName(); //bobv console.log(instance instanceof Object); //true console.log(instance instanceof childClass);//true console.log(instance instanceof superClass);//true console.log(Object.prototype.isPrototypeOf(instance));//true console.log(superClass.prototype.isPrototypeOf(instance));//true console.log(childClass.prototype.isPrototypeOf(instance));//true
和其餘的狀況同樣,一旦使用到prototype,就不建議使用字面量的方式爲原型添加屬性了。腦補便可。
同時此種不加區分的繼承方式,任然保留內存共享的問題(引用類型屬性的問題)。同原型模式建立對象的問題是同樣的。固然解決這個問題的辦法和解決建立對象時的方法也是同樣的,那就是混合使用構造和原型來實現繼承。
function superClass(name){ this.name=name; this.number=[1,2,4]; } superClass.prototype.getName=function(){ console.log(this.name); } function childClass(name,age){ //繼承屬性 superClass.call(this,name); this.age=age; } childClass.prototype = new superClass(); childClass.prototype.getName = function(){ console.log(this.name+"v"); } var instance = new childClass("huazi",22); //重寫了父類方法和屬性 instance.getName(); //huaziv instance.number.push("1"); instance.number===new childClass().number; //false instance.number//[1,2,4,'1']
此種方法的原則就是屬性靠構造,方法靠原型。能夠簡單地這麼理解一下。此種模式也有個問題就是效率沒有達到極致,由於屢次調用了父類構造。還有一個讓人不舒服的地方就是原型和實例上都包括有name和age屬性。這是不能接受的。
最後放一個大招,叫作寄生組合模式。其實也就是避免了上面這個方式的缺點,即繞過父類構造來繼承父類原型。
/** * 1,找到父類原型對象 * 2,修改構造的指向 * 3,父類原型 */ function inheritPrototype(superClass,childClass){ var _prototype = new Object(superClass.prototype); _prototype.constructor = childClass; childClass.prototype=_prototype; } function superClass(name){ this.name=name; this.number=[1,2,4]; } superClass.prototype.getName=function(){ console.log(this.name); } function childClass(name,age){ //繼承屬性 superClass.call(this,name); this.age=age; } //不一樣點就再這 inheritPrototype(superClass,childClass); childClass.prototype.getName = function(){ console.log(this.name+"v"); } var instance = new childClass("huazi",22); //重寫了父類方法和屬性 instance.getName(); //huaziv instance.number.push("1"); instance.number===new childClass().number; //false instance.number//[1,2,4,'1']
噁心死我了,終於寫完了!