說到對象,我想到一個成語叫作談虎色變,對象應該說是javascript中最難得一部分,原因呢,首先,js作為函數式編程語言,對象的實現方式跟java,c,c++等面向對象設計語言不一樣;其次js面向對象編程屬于高級程序員應該掌握的,對于初學者來講,沒有實踐基礎,憑空理解更是難上加難。不過不用擔心,我也不怎么會,我們就一起來探討一下吧。
我們舉個例子,大千世界,人心難測。每個人都是獨立的,有自己的思維,性別,年齡,會跑,會跳等等,人就是一個單位,一個整體,然后人與人之間才會有血緣關系,社會關系。試想,一個人都不完整,還談什么其他東西呢?所以我們的首先任務就是“造人”,造人的方式的有很多種,當然肯定不是你想的那種,哈哈!好,接下來,開始造人吧!
工廠模式的原理:在函數內部創建object對象,對象屬性由參數指定,方法也掛在object對象上,最后返回這個object對象。相當于Person函數是一個造人工廠,一下子造了Tom,Linda兩個人。但是缺點是不知道這兩個人什么類型的,你會說都是object啊,對啊,你回答這個答案就像我問你你現在在哪?你說你在地球上一樣的道理。我們應該知道,js里所有事物都是object類型,所以為了搞明白這兩個人具體的類型,我們另有他法,那就是構造函數模式。
function Person(name,sex){ var o = new Object(); o.name = name; o.sex = sex; o.say = function(){ console.log(this.name + ' is '+ this.sex) }; return o;}var Tom = Person('Tom','male');var Linda = Person('Linda','female');構造函數模式相比工廠函數就是知道Tom和Linda這兩個人是Person類型,原理是new關鍵字默認執行了以下操作: 1.創建一個全新的對象 2.這個對象會被執行[[PRototype]]連接原型 3.函數調用中的this會綁定到新對象 4.如果函數沒有返回其他對象,那么new 構造就會自動返回這個新對象 注意:這里我特意加粗這句話,因為文末講的寄生構函數模式的理解需要仰仗這句話。先透個底,如果函數像工廠函數那樣返回了對象,那么new關鍵詞也就不會執行默認操作。
function Person(name,sex){ this.name = name; this.sex = sex; this.say = function(){ console.log(this.name + ' is '+ this.sex); }}var Tom = new Person('Tom','male');var Linda = new Person('Linda','female');上述的原型鏈圖示為:
Person.prototype指向原型對象,而原型對象的constructor屬性指向構造函數Person,Tom和Linda實例繼承了原型對象的constructor屬性,所以有下面的等式
console.log(Tom.constructor === Person.prototype.constructor && Tom.constructor === Person);//tureconsole.log(Linda.constructor === Person.prototype.constructor && Linda.constructor === Person);//tureJavascript還提供了instanceof,驗證實例與構造函數的關系
console.log(Tom instanceof Person);//trueconsole.log(Linda instanceof Person);//true構造函數有個弊端,就是每一次new一個對象的時候,其實都創建了各自的屬性和方法,這些屬性和方法是重復的,完全沒必要嘛,代碼量多,又浪費內存。原型模式解決了這個問題,將公用方法和不變的屬性掛在原型對象上。
//構造函數模式new的實例,相同的屬性和方法是不一致的,驗證一下console.log(Tom.name === Linda.name);//falseconsole.log(Tom.sex === Linda.sex);//falseconsole.log(Tom.say === Linda.say);//false//不對就對了,下面是原型模式function Person(){}Person.prototype.name = 'Tom';Person.prototype.sex = 'male';Person.prototype.say = function(){ console.log(this.name + ' is '+ this.sex);};var Tom = new Person();var Linda = new Person();原型鏈圖示為:
實例屬性或方法的訪問過程是一次搜索過程: 1.首先從對象實例本身開始,如果找到屬性就直接返回該屬性值; 2.如果實例本身不存在要查找屬性,就繼續搜索指針指向的原型對象,在其中查找給定名字的屬性,如果有就返回; 基于以上分析,原型模式創建的對象實例,其屬性是共享原型對象的;但也可以自己實例中再進行定義,在查找時,就不從原型對象獲取,而是根據搜索原則,得到本實例的返回;簡單來說,就是實例中屬性會屏蔽原型對象中的屬性;
接下來看一下這些屬性和方法相等嗎?
console.log(Tom.name === Linda.name);//trueconsole.log(Tom.sex === Linda.sex);//trueconsole.log(Tom.say === Linda.say);//truejavascript提供一些驗證實例和原型對象關系的方法
//isPrototypeOf用來判斷,某個原型對象和某個實例之間的關系console.log(Person.prototype.isPrototypeOf(Tom)); //trueconsole.log(Person.prototype.isPrototypeOf(Linda)); //true//hasOwnProperty()方法,用來判斷某一個屬性到底是本地屬性,還是繼承自prototype對象的屬性console.log(Tom.hasOwnProperty("name")); // falseconsole.log(Linda.hasOwnProperty("name")); // false//in運算符可以用來判斷,某個實例是否含有某個屬性,不管是不是本地屬性console.log("name" in Tom); // trueconsole.log("name" in Linda); // true簡稱組合模式,構造函數實例好比私有制,原型模式實例好比公有制,那么組合模式就是以公有制為核心,私有制并行的模式,佩服我歷史學得真好!所以自己的屬性就不要掛在原型對象上,只有共有屬性和方法才掛在上面。
function Person(name,sex){ this.name = name; this.sex = sex;}Person.prototype = { //原型字面量方式會將對象的constructor變為Object,此外強制指回Person constructor: Person, say: function(){ console.log(this.name + ' is '+ this.sex); }}var Tom = new Person('Tom','male');var Linda = new Person('Linda','female');原型鏈示圖:
做些測試吧!
console.log(Tom.name);//Tomconsole.log(Linda.name);//Lindaconsole.log(Tom.say === Linda.say);//true組合模式私有屬性和共有屬性及方法是分離的,如果能夠放在一個構造函數里,那就真的和對象相差無幾了。
function Person(name,sex){ this.name = name; this.sex = sex; if(typeof this.say != 'function'){ Person.prototype = { constructor: Person, say: function(){ console.log(this.name + ' is '+ this.sex); } } }}var Tom = new Person('Tom','male');var Linda = new Person('Linda','female');來驗證一下吧!
console.log(Tom.name);//Tomconsole.log(Linda.name);//Linda//===>想想為什么第一個Tom.say返回undefined?我似懂非懂Tom.say;//undefinedLinda.say;//function還記得講構造函數模式的時候,new的實質嗎?溫習一下: new的實質是執行了以下操作 1.創建一個全新的對象 2.這個對象會被執行[[prototype]]連接原型 3.函數調用中的this會綁定到新對象 4.如果函數沒有返回其他對象,那么new 構造就會自動返回這個默認對象 注意:如果返回了其他對象,那么new關鍵詞也就不會執行默認操作。
現在有個疑問:以上以new創建實例的模式都沒有返回新對象,而是依靠new的默認操作創建的對象實例。但是,but,but,new默認返回的對象是object類型,如果我現在要返回array或者其他對象,new就不能再執行其默認操作了,所以就要用return語句重寫構造函數,返回期望的對象類型。
function SpecialArray(){ var values = new Array(); values.push.apply(values, arguments); values.toPipedString = function(){ return this.join("|"); }; return values; }var a = new SpecialArray(2,6,8,9,4);a.toPipedString();//2|6|8|9|4上面是網上隨便找的一段關于創建數組對象的例子,我們還是改寫我們的例子
function Person(name,sex){ var o = new Object(); o.name = name; o.sex = sex; o.say = function(){ console.log(this.name + ' is '+ this.sex) }; return o;}//與工廠模式的區別就是調用的時候加上了new關鍵字,工廠模式直接調用函數var Tom = new Person('Tom','male');var Linda = new Person('Linda','female');那么就有人問了,這寄生構造函數模式又產生什么結果呢?其實答案和工廠模式輸出結果一模一樣。那有了工廠模式了,寄生構造模式是不是多余的呢?借用高程書上的原話: 書上原話:除了使用new操作符并把使用的包裝函數叫做構造函數之外,這個模式跟工廠模式其實是一模一樣的。構造函數在不返回值的情況下。默認會返回新對象實例。而通過在構造函數的末尾添加一個return語句,可以重寫調用構造函數時返回的值。
對象涉及封裝和繼承,繼承放在后一片文章去講。弄了這么一通,對象封裝也就6個模式,寄生構造函數模式跟工廠模式沒有什么大區別,而且我們經常用的是組合模式和動態原型模式。所以實踐看起來是要比學習單純許多。哈哈
新聞熱點
疑難解答