學(xué)習(xí)JavaScript設(shè)計(jì)模式(封裝)
在JavaScript 中,并沒有對(duì)抽象類和接口的支持。JavaScript 本身也是一門弱類型語言。在封裝類型方面,JavaScript 沒有能力,也沒有必要做得更多。對(duì)于JavaScript 的設(shè)計(jì)模式實(shí)現(xiàn)來說,不區(qū)分類型是一種失色,也可以說是一種解脫。
從設(shè)計(jì)模式的角度出發(fā),封裝在更重要的層面體現(xiàn)為封裝變化。
通過封裝變化的方式,把系統(tǒng)中穩(wěn)定不變的部分和容易變化的部分隔離開來,在系統(tǒng)的演變過程中,我們只需要替換那些容易變化的部分,如果這些部分是已經(jīng)封裝好的,替換起來也相對(duì)容易。這可以最大程度地保證程序的穩(wěn)定性和可擴(kuò)展性。
javascript封裝的的基本模式有3種:
1、使用約定優(yōu)先的原則,將所有的私有變量以_開頭
<script type="text/javascript">
/**
* 使用約定優(yōu)先的原則,把所有的私有變量都使用_開頭
*/
var Person = function (no, name, age)
{
this.setNo(no);
this.setName(name);
this.setAge(age);
}
Person.prototype = {
constructor: Person,
checkNo: function (no)
{
if (!no.constructor == "string" || no.length != 4)
throw new Error("學(xué)號(hào)必須為4位");
},
setNo: function (no)
{
this.checkNo(no);
this._no = no;
},
getNo: function ()
{
return this._no;
setName: function (name)
{
this._name = name;
},
getName: function ()
{
return this._name;
},
setAge: function (age)
{
this._age = age;
},
getAge: function ()
{
return this._age;
},
toString: function ()
{
return "no = " + this._no + " , name = " + this._name + " , age = " + this._age;
}
};
var p1 = new Person("0001", "小平果", "22");
console.log(p1.toString()); //no = 0001 , name = 小平果 , age = 22
p1.setNo("0003");
console.log(p1.toString()); //no = 0003 , name = 小平果 , age = 22
p1.no = "0004";
p1._no = "0004";
console.log(p1.toString()); //no = 0004 , name =小平果 , age = 22
</script>
看完代碼,是不是有種被坑的感覺,僅僅把所有的變量以_開頭,其實(shí)還是可以直接訪問的,這能叫封裝么,當(dāng)然了,說了是約定優(yōu)先嘛。
下劃線的這種用法這一個(gè)眾所周知的命名規(guī)范,它表明一個(gè)屬性僅供對(duì)象內(nèi)部使用,直接訪問它或設(shè)置它可能會(huì)導(dǎo)致意想不到的后果。這有助于防止程序員對(duì)它的無意使用,卻不能防止對(duì)它的有意使用。
這種方式還是不錯(cuò)的,最起碼成員變量的getter,setter方法都是prototype中,并非存在對(duì)象中,總體來說還是個(gè)不錯(cuò)的選擇。如果你覺得,這不行,必須嚴(yán)格實(shí)現(xiàn)封裝,那么看第二種方式。
2、嚴(yán)格實(shí)現(xiàn)封裝
<script type="text/javascript">
/**
* 使用這種方式雖然可以嚴(yán)格實(shí)現(xiàn)封裝,但是帶來的問題是get和set方法都不能存儲(chǔ)在prototype中,都是存儲(chǔ)在對(duì)象中的
* 這樣無形中就增加了開銷
*/
var Person = function (no, name, age)
{
var _no , _name, _age ;
var checkNo = function (no)
{
if (!no.constructor == "string" || no.length != 4)
throw new Error("學(xué)號(hào)必須為4位");
};
this.setNo = function (no)
{
checkNo(no);
_no = no;
};
this.getNo = function ()
{
return _no;
}
this.setName = function (name)
{
_name = name;
}
this.getName = function ()
{
return _name;
}
this.setAge = function (age)
{
_age = age;
}
this.
getAge = function ()
{
return _age;
}
this.setNo(no);
this.setName(name);
this.setAge(age);
}
Person.prototype = {
constructor: Person,
toString: function ()
{
return "no = " + this.getNo() + " , name = " + this.getName() + " , age = " + this.getAge();
}
}
;
var p1 = new Person("0001", "小平果", "22");
console.log(p1.toString()); //no = 0001 , name =小平果 , age = 22
p1.setNo("0003");
console.log(p1.toString()); //no = 0003 , name = 小平果 , age = 22
p1.no = "0004";
console.log(p1.toString()); //no = 0003 , name = 小平果 , age = 22
</script>
那么這與我們先前講過的其他創(chuàng)建對(duì)象的模式有什么不同呢,在上面的例子中,我們?cè)趧?chuàng)建和引用對(duì)象的屬性時(shí)總要使用this關(guān)鍵字。而在本例中,我們用var聲明這些變量。這意味著它們只存在于Person構(gòu)造器中。checkno函數(shù)也是用同樣的方式聲明的,因此成了一個(gè)私用方法。
需要訪問這些變量和函數(shù)的方法只需要聲明在Person中即可。這些方法被稱為特權(quán)方法,因?yàn)樗鼈兪枪梅椒?,但卻能夠訪問私用屬性和方法。為了在對(duì)象外部能訪問這些特權(quán)函數(shù),它們的前面被加上了關(guān)鍵字this。因?yàn)檫@些方法定義于Person構(gòu)造器的作用域,所以它們能訪問到私用屬性。引用這些屬性時(shí)并沒有使用this關(guān)鍵字,因?yàn)樗鼈儾皇枪_的。所有取值器和賦值器方法都被改為不加this地直接引用這些屬性。
任何不需要直接訪問的私用屬性的方法都可以像原來那樣在Person.prototype中聲明。像toString()方法。只有那些需要直接訪問私用成員的方法才應(yīng)該被設(shè)計(jì)為特權(quán)方法。但特權(quán)方法太多又會(huì)占用過多的內(nèi)存,因?yàn)槊總€(gè)對(duì)象實(shí)例都包含所有特權(quán)方法的新副本。
看上面的代碼,去掉了this.屬性名,嚴(yán)格的實(shí)現(xiàn)了封裝,只能通過getter,setter訪問成員變量了,但是存在一個(gè)問題,所有的方法都存在對(duì)象中,增加了內(nèi)存的開銷。
3、以閉包的方式封裝
<script type="text/javascript">
var Person = (function ()
{
//靜態(tài)方法(共享方法)
var checkNo = function (no)
{
if (!no.constructor == "string" || no.length != 4)
throw new Error("學(xué)號(hào)必須為4位");
};
//靜態(tài)變量(共享變量)
var times = 0;
//return the constructor.
return function (no, name, age)
{
console.log(times++); // 0 ,1 , 2
var no , name , age; //私有變量
this.setNo = function (no) //私有方法
{
checkNo(no);
this._no = no;
};
this.getNo = function ()
{
return this._no;
}
this.setName = function (name)
{
this._name = name;
}
this.getName = function ()
{
return this._name;
}
this.setAge = function (age)
{
this._age = age;
}
this.getAge = function ()
{
return this._age;
}
this.setNo(no);
this.setName(name);
this.setAge(age);
}
})();
Person.prototype = {
constructor: Person,
toString: function ()
{
return "no = " + this._no + " , name = " + this._name + " , age = " + this._age;
}
};
var p1 = new Person("0001", "小平果", "22");
var p2 = new Person("0002", "abc", "23");
var p3 = new Person("0003", "aobama", "24");
console.log(p1.toString()); //no = 0001 , name = 小平果 , age = 22
console.log(p2.toString()); //no = 0002 , name = abc , age = 23
console.log(p3.toString()); //no = 0003 , name = aobama , age = 24
</script>
上述代碼,js引擎加載完后,會(huì)直接執(zhí)行Person = 立即執(zhí)行函數(shù),然后此函數(shù)返回了一個(gè)子函數(shù),這個(gè)子函數(shù)才是new Person所調(diào)用的構(gòu)造函數(shù),又因?yàn)樽雍瘮?shù)中保持了對(duì)立即執(zhí)行函數(shù)中checkNo(no) ,times的引用,(很明顯的閉包)所以對(duì)于checkNo和times,是所有Person對(duì)象所共有的,創(chuàng)建3個(gè)對(duì)象后,times分別為0,1,2 。這種方式的好處是,可以使Person中需要復(fù)用的方法和屬性做到私有且對(duì)象間共享。
這里的私用成員和特權(quán)成員仍然被聲明在構(gòu)造器。但那個(gè)構(gòu)造器卻從原來的普通函數(shù)變成了一個(gè)內(nèi)嵌函數(shù),并且被作為包含它的函數(shù)的返回值給變量Person。這就創(chuàng)建了一個(gè)閉包,你可以把靜態(tài)的私用成員聲明在里面。位于外層函數(shù)聲明之后的一對(duì)空括號(hào)很重要,其作用是代碼一載入就立即執(zhí)行這個(gè)函數(shù)。這個(gè)函數(shù)的返回值是另一個(gè)函數(shù),它被賦給Person變量,Person因此成了一個(gè)構(gòu)造函數(shù)。在實(shí)例華Person時(shí),所調(diào)用的這個(gè)內(nèi)層函數(shù)。外層那個(gè)函數(shù)只是用于創(chuàng)建一個(gè)可以用來存儲(chǔ)靜態(tài)成員的閉包。
在本例中,checkno被設(shè)計(jì)成為靜態(tài)方法,原因是為Person的每個(gè)實(shí)例都生成這個(gè)方法的一個(gè)新副本毫無道理。此外還有一個(gè)靜態(tài)屬性times,其作用在于跟蹤Person構(gòu)造器的總調(diào)用次數(shù)。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,大家可以更深入的學(xué)習(xí)了解封裝的意義。
- 不錯(cuò)的一篇關(guān)于javascript-prototype繼承
- javascript prototype的深度探索不是原型繼承那么簡(jiǎn)單
- 實(shí)現(xiàn)JavaScript中繼承的三種方式
- 小議javascript 設(shè)計(jì)模式 推薦
- javascript 設(shè)計(jì)模式之單體模式 面向?qū)ο髮W(xué)習(xí)基礎(chǔ)
- JavaScript類和繼承 this屬性使用說明
- JavaScript原型繼承之基礎(chǔ)機(jī)制分析
- javascript設(shè)計(jì)模式 接口介紹
- 深入了解javascript中的prototype與繼承
- 深入理解JavaScript是如何實(shí)現(xiàn)繼承的
- JavaScript繼承基礎(chǔ)講解(原型鏈、借用構(gòu)造函數(shù)、混合模式、原型式繼承、寄生式繼承、寄生組合式繼承)
- JavaScript設(shè)計(jì)模式學(xué)習(xí)之“類式繼承”
- 學(xué)習(xí)JavaScript設(shè)計(jì)模式(多態(tài))
- 學(xué)習(xí)JavaScript設(shè)計(jì)模式(接口)
- 學(xué)習(xí)JavaScript設(shè)計(jì)模式(繼承)
相關(guān)文章
Js保留小數(shù)點(diǎn)的4種效果實(shí)現(xiàn)代碼分享
jvascript 計(jì)算保留小數(shù)點(diǎn)一兩位,有四種不同效果,非常適用于商城類網(wǎng)站,需要的朋友可以參考下2014-04-04
IE和Firefox的Javascript兼容性總結(jié)[推薦收藏]
長久以來JavaScript兼容性一直是Web開發(fā)者的一個(gè)主要問題。在正式規(guī)范、事實(shí)標(biāo)準(zhǔn)以及各種實(shí)現(xiàn)之間的存在的差異讓許多開發(fā)者日夜煎熬2011-10-10
BootstrapTable與KnockoutJS相結(jié)合實(shí)現(xiàn)增刪改查功能【一】
KnockoutJS是一個(gè)JavaScript實(shí)現(xiàn)的MVVM框架。通過本文給大家介紹BootstrapTable與KnockoutJS相結(jié)合實(shí)現(xiàn)增刪改查功能【一】,感興趣的朋友一起學(xué)習(xí)吧2016-05-05
一文讓你徹底搞清楚javascript中的require、import與export
這篇文章主要給大家介紹了關(guān)于javascript中require、import與export的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)打擊大的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-09-09
JS實(shí)現(xiàn)的簡(jiǎn)潔縱向滑動(dòng)菜單(滑動(dòng)門)效果
這篇文章主要介紹了JS實(shí)現(xiàn)的簡(jiǎn)潔縱向滑動(dòng)菜單(滑動(dòng)門)效果,通過簡(jiǎn)單的頁面元素遍歷實(shí)現(xiàn)華東切換的功能,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10
JavaScript前端靜態(tài)資源預(yù)加載實(shí)現(xiàn)示例
這篇文章主要為大家介紹了JavaScript前端靜態(tài)資源預(yù)加載實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11
使用JS location實(shí)現(xiàn)搜索框歷史記錄功能
這篇文章主要介紹了使用JS location實(shí)現(xiàn)搜索框歷史記錄功能,本文通過實(shí)例 代碼講解的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-12-12

