本文實(shí)例講述了JS中創(chuàng)建自定義類型的常用模式。分享給大家供大家參考,具體如下:
為彝良等地區(qū)用戶提供了全套網(wǎng)頁(yè)設(shè)計(jì)制作服務(wù),及彝良網(wǎng)站建設(shè)行業(yè)解決方案。主營(yíng)業(yè)務(wù)為成都網(wǎng)站建設(shè)、成都網(wǎng)站制作、彝良網(wǎng)站設(shè)計(jì),以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠(chéng)的服務(wù)。我們深信只要達(dá)到每一位用戶的要求,就會(huì)得到認(rèn)可,從而選擇與我們長(zhǎng)期合作。這樣,我們也可以走得更遠(yuǎn)!
雖然在 ES6 中,已經(jīng)出了 class 的語(yǔ)法,貌似好像不用了解 ES5 中的這些老東西了,但是越深入學(xué)習(xí),你會(huì)發(fā)現(xiàn)理解這些模式的重要性。
在本文中,我會(huì)描述 7 種常用的創(chuàng)建自定義類型的模式:工廠模式、構(gòu)造函數(shù)模式、原型模式、組合使用構(gòu)造函數(shù)模式、動(dòng)態(tài)原型模式、寄生構(gòu)造函數(shù)模式、穩(wěn)妥構(gòu)造函數(shù)模式。分別給出他們的示例代碼,并分析他們的利弊,方便讀者選擇具體的方式來(lái)構(gòu)建自己的自定義類型。
最后,我會(huì)指出 ES6 中的 class 語(yǔ)法,本質(zhì)上其實(shí)還是利用了組合使用構(gòu)造函數(shù)模式進(jìn)行創(chuàng)建自定義類型。
1. 工廠模式
廢話不多說,先上工廠模式的實(shí)例代碼:
function createPerson(name, age, job){ var o = new Object(); // 創(chuàng)建對(duì)象 o.name = name; // 賦予對(duì)象細(xì)節(jié) o.age = age; // 賦予對(duì)象細(xì)節(jié) o.job = job; // 賦予對(duì)象細(xì)節(jié) o.sayName = function(){ // 賦予對(duì)象細(xì)節(jié) alert(this.name); }; return o; // 返回該對(duì)象 } var person1 = createPerson("Nicholas", 29, "Software Engineer"); var person2 = createPerson("Greg", 27, "Doctor");
優(yōu)點(diǎn):解決了創(chuàng)建多個(gè)相似對(duì)象的問題;
缺點(diǎn):沒有解決對(duì)象識(shí)別的問題(即不知道這個(gè)對(duì)象是什么類型),對(duì)于對(duì)象的方法沒有做到復(fù)用。
2. 構(gòu)造函數(shù)模式
function Person(name, age, job){ this.name = name; // 對(duì)象的所有細(xì)節(jié)全部掛載在 this 對(duì)象下面 this.age = age; this.job = job; this.sayName = function(){ alert(this.name); }; } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor");
說到構(gòu)造函數(shù)模式就不得不提到 new 操作符了。我們來(lái)看看 new 這個(gè)操作符到底做了什么:
① 創(chuàng)建一個(gè)對(duì)象;
② 將構(gòu)造函數(shù)內(nèi)的 this 指向這個(gè)新創(chuàng)建的對(duì)象,同時(shí)將該函數(shù)的 prototype 的引用掛載在新對(duì)象的原型下;
③ 執(zhí)行函數(shù)內(nèi)的細(xì)節(jié),也就是將屬性和方法掛載在新對(duì)象下;
④ 隱式的返回新創(chuàng)建的對(duì)象。
優(yōu)點(diǎn):解決了對(duì)象識(shí)別的問題;
缺點(diǎn):對(duì)于自定義類型的方法每次都要新創(chuàng)建一個(gè)方法函數(shù)實(shí)例,沒有做到函數(shù)復(fù)用。如果把所有方法函數(shù)寫到父級(jí)作用域中,是做到了函數(shù)復(fù)用,但同時(shí)方法函數(shù)只能在父級(jí)作用域的某個(gè)類型中進(jìn)行調(diào)用,這對(duì)于父級(jí)作用域有點(diǎn)名不副實(shí),同時(shí)對(duì)于自定義引用類型沒有封裝性可言。
3. 原型模式
function Person(){ } Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){ alert(this.name); }; var person1 = new Person(); person1.sayName(); //"Nicholas" var person2 = new Person(); person2.sayName(); //"Nicholas" alert(person1.sayName == person2.sayName); //true
理解要點(diǎn):
① 無(wú)論什么時(shí)候,只要?jiǎng)?chuàng)建了一個(gè)新函數(shù),就會(huì)根據(jù)一組特定規(guī)則為該函數(shù)創(chuàng)建一個(gè) prototype 屬性,這個(gè)屬性指向函數(shù)的原型對(duì)象。
② 在默認(rèn)情況下,所有原型對(duì)象都會(huì)自動(dòng)獲得一個(gè) constructor 屬性,這個(gè)屬性包含一個(gè)指向 prototype 屬性所在函數(shù)的指針。至于原型中的其他方法則都是從 Object 繼承而來(lái)。
③ 當(dāng)調(diào)用構(gòu)造函數(shù)創(chuàng)建了一個(gè)新實(shí)例后,該實(shí)例的內(nèi)部將包含一個(gè)指針 [[prototype]](內(nèi)部屬性) ,指向構(gòu)造函數(shù)的原型對(duì)象。
④ 當(dāng)調(diào)用構(gòu)造函數(shù)創(chuàng)建一個(gè)新實(shí)例后,該實(shí)例的實(shí)例環(huán)境,即構(gòu)造函數(shù),會(huì)針對(duì)原型對(duì)象上的非引用類型的原型屬性,在構(gòu)造函數(shù)中自動(dòng)構(gòu)建相應(yīng)的實(shí)例環(huán)境屬性。也就是說,之后根據(jù)構(gòu)造函數(shù)創(chuàng)建的實(shí)例,它的實(shí)例屬性中的非引用類型屬性,都仍是根據(jù)構(gòu)造函數(shù)中的實(shí)例環(huán)境屬性創(chuàng)建的。
但是為減少不必要的輸入,也為了從視覺上更好地封裝原型的功能,更常見的做法是用一個(gè)包含所有屬性和方法的對(duì)象字面量來(lái)重寫整個(gè)原型對(duì)象。如下所示:
function Person(){ } Person.prototype = { name : "Nicholas", age : 29, job: "Software Engineer", sayName : function () { alert(this.name); } };
但是這種寫法,其本質(zhì)上完全重寫了默認(rèn)的 prototype 對(duì)象,因此 constrctor 屬性也就變成了新對(duì)象的 constructor 屬性(指向 Object 構(gòu)造函數(shù)),不在指向 Person 函數(shù)。盡管此時(shí),instanceOf 操作符還能返回正確的結(jié)果。
如果 constructor 屬性真的很重要,可以像下面這樣特意將它設(shè)置回適當(dāng)?shù)闹担?/p>
function Person(){ } Person.prototype = { constructor : Person, name : "Nicholas", age : 29, job: "Software Engineer", sayName : function () { alert(this.name); } };
注意,以這種方式重設(shè) constructor 屬性會(huì)導(dǎo)致他的 [[Enumerable]] 特性被設(shè)置為 true 。默認(rèn)情況下,原生的 constructor 屬性是不可枚舉的,因此,如果你使用兼容 ECMAScript 5 的 JavaScript 引擎,你可以試試 Object.defineProperty()
方法:
function Person(){ } Person.prototype = { name : "Nicholas", age : 29, job : "Software Engineer", sayName : function () { alert(this.name); } }; //重設(shè)構(gòu)造函數(shù),只適用于 ECMAScript 5 兼容的瀏覽器 Object.defineProperty( Person.prototype, "constructor", { enumerable: false, value: Person });
注意,重寫原型對(duì)象會(huì)切斷新原型與已經(jīng)存在的對(duì)象實(shí)例之間的聯(lián)系;它們引用的仍然是最初的原型。
優(yōu)點(diǎn):對(duì)自定義類型的方法解決了函數(shù)復(fù)用的問題。
缺點(diǎn):
① 不能為構(gòu)造函數(shù)傳遞初始化參數(shù);
② 原型模式中實(shí)現(xiàn)了對(duì)于包含引用類型值的屬性的共享,這就意味著一個(gè)實(shí)例中修改了該引用類型值,所有實(shí)例的該屬性都會(huì)被修改?。。?/p>
4. 組合使用構(gòu)造函數(shù)模式和原型模式
在組合使用構(gòu)造函數(shù)模式和原型模式中,構(gòu)造函數(shù)模式用于定義實(shí)例屬性,而原型模式用于定義方法和共享的屬性,而且還支持向構(gòu)造函數(shù)傳遞參數(shù)。如以下示例代碼所示:
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.friends = ["Shelby", "Court"]; } Person.prototype = { sayName : function(){ alert(this.name); } } Object.defineProperty( Person.prototype, "constructor", { enumerable: false, value: Person ); var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor"); person1.friends.push("Van"); alert(person1.friends); //"Shelby,Count,Van" alert(person2.friends); //"Shelby,Count" alert(person1.friends === person2.friends); //false alert(person1.sayName === person2.sayName); //true
優(yōu)點(diǎn):能為構(gòu)造函數(shù)傳遞初始化參數(shù);該復(fù)用復(fù)用,不該復(fù)用的沒復(fù)用。
缺點(diǎn):封裝性不好,構(gòu)造函數(shù)和原型分別獨(dú)立于父級(jí)作用域進(jìn)行申明。
5. 動(dòng)態(tài)原型模式(推薦)
該模式把所有信息都封裝在構(gòu)造函數(shù)中,通過構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)初始化原型 (僅在必要的情況下),又保持了同時(shí)使用構(gòu)造函數(shù)和原型的優(yōu)點(diǎn)。請(qǐng)看以下示例代碼:
function Person(name, age, job){ //屬性 this.name = name; this.age = age; this.job = job; //方法 if (typeof this.sayAge != "function"){ // 此處應(yīng)該永遠(yuǎn)去判斷新添加的屬性和方法 Person.prototype.sayName = function(){ alert(this.name); }; Person.prototype.sayAge = function(){ alert(this.age); }; } } var friend = new Person("Nicholas", 29, "Software Engineer"); friend.sayName();
if 語(yǔ)句檢查的可以是初始化之后應(yīng)該存在的任何屬性或方法——不必用一大堆 if 語(yǔ)句檢查每個(gè)屬性和每個(gè)方法;只要檢查其中一個(gè)即可。
注意,使用動(dòng)態(tài)原型模式時(shí),不能使用對(duì)象字面量重寫原型。前面已經(jīng)解釋過了,如果已經(jīng)創(chuàng)建的實(shí)例的情況下重寫原型,那么就會(huì)切斷新原型與現(xiàn)有實(shí)例之間的聯(lián)系。
優(yōu)點(diǎn):封裝性非常好;還可使用 instanceOf
操作符確定它的類型。
缺點(diǎn):無(wú)。
6. 寄生構(gòu)造函數(shù)模式
除了使用 new 操作符并把使用的包裝函數(shù)叫做構(gòu)造函數(shù)之外,這個(gè)模式跟工廠模式其實(shí)是一模一樣的。請(qǐng)看以下代碼:
function Person(name, age, job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(this.name); }; return o; } var friend = new Person("Nicholas", 29, "Software Engineer"); friend.sayName(); //"Nicholas"
在使用 new
操作符下,構(gòu)造函數(shù)在不返回值的情況下,默認(rèn)會(huì)返回新對(duì)象實(shí)例。而通過在構(gòu)造函數(shù)的末尾添加一個(gè) return 語(yǔ)句,可以重寫調(diào)用構(gòu)造函數(shù)時(shí)返回的值。
缺點(diǎn):沒有解決對(duì)象識(shí)別的問題(即不知道這個(gè)對(duì)象是什么類型),不能依賴 instanceOf
操作符來(lái)確定對(duì)象類型;對(duì)于對(duì)象的方法沒有做到復(fù)用。
7. 穩(wěn)妥構(gòu)造函數(shù)模式
先來(lái)了解下穩(wěn)妥對(duì)象:指的是沒有公共屬性,而且其方法也不引用 this 的對(duì)象。穩(wěn)妥對(duì)象最適合在一些安全的環(huán)境中 (這些環(huán)境中會(huì)禁止使用 this 和 new),或者再防止數(shù)據(jù)被其他應(yīng)用程序 (如 Mashup 程序) 改動(dòng)時(shí)使用。穩(wěn)妥構(gòu)造函數(shù)遵循與寄生構(gòu)造函數(shù)類似的模式,但有兩點(diǎn)不同:一是新創(chuàng)建對(duì)象的實(shí)例方法不引用 this;二是不使用 new 操作符調(diào)用構(gòu)造函數(shù)。以下為示例代碼:
function Person(name, age, job){ var o = new Object(); //創(chuàng)建要返回的對(duì)象 //可以在這里定義私有變量和函數(shù) o.sayName = function(){ //添加方法 alert(name); }; return o; //返回對(duì)象 } var friend = Person("Nicholas", 29, "Software Engineer"); friend.sayName(); //"Nicholas"
其原理就是利用閉包,保有對(duì)私有變量和私有方法的引用。
優(yōu)點(diǎn):不可能有別的方法訪問到傳入到構(gòu)造函數(shù)中的原始數(shù)據(jù)。
缺點(diǎn):沒有解決對(duì)象識(shí)別的問題(即不知道這個(gè)對(duì)象是什么類型),不能依賴 instanceOf
操作符來(lái)確定對(duì)象類型;對(duì)于對(duì)象的方法沒有做到復(fù)用。
8. ES6 中的 class
咱們這塊以 class 實(shí)例來(lái)展開講述:
class Parent { name = "qck"; sex = "male"; //實(shí)例變量 sayHello(name){ console.log('qck said Hello!',name); } constructor(location){ this.location = location; } }
我們來(lái)看看這段代碼通過 babel 編譯后的 _createClass 函數(shù):
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; // 對(duì)屬性進(jìn)行數(shù)據(jù)特性設(shè)置 descriptor.enumerable = descriptor.enumerable || false; // enumerable設(shè)置 descriptor.configurable = true; // configurable設(shè)置 if ("value" in descriptor) descriptor.writable = true; // 如果有value,那么可寫 Object.defineProperty(target, descriptor.key, descriptor); // 調(diào)用defineProperty() 進(jìn)行屬性設(shè)置 } } return function (Constructor, protoProps, staticProps) { // 設(shè)置到第一個(gè) Constructor 的 prototype 中 if (protoProps) defineProperties(Constructor.prototype, protoProps); // 設(shè)置 Constructor 的 static 類型屬性 if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
首先該方法是一個(gè)自執(zhí)行函數(shù),接收的一參是構(gòu)造函數(shù)本身,二參是為構(gòu)造函數(shù)的原型對(duì)象需要添加的方法或者屬性,三參是需要為構(gòu)造函數(shù)添加的靜態(tài)屬性對(duì)象。從這個(gè)函數(shù)就可以看出 class 在創(chuàng)建自定義類型時(shí),用了原型模式。
我們看看編譯后的結(jié)果是如何調(diào)用 _createClass 的:
var Parent = function () { // 這里是自執(zhí)行函數(shù) _createClass(Parent, [{ // Parent的實(shí)例方法,通過修改Parent.prototype來(lái)完成 key: "sayHello", value: function sayHello(name) { console.log('qck say Hello!', name); } }]); function Parent(location) { //在Parent構(gòu)造函數(shù)中添加實(shí)例屬性 _classCallCheck(this, Parent); this.name = "qck"; this.sex = "male"; this.location = location; } return Parent; }(); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
這里調(diào)用 _createClass 的地方就證實(shí)了我們剛才的想法——確實(shí)應(yīng)用了原型模式:我們的 class 上的方法,其實(shí)是通過修改該類 (實(shí)際上是函數(shù)) 的 prototype 來(lái)完成的。
而通過返回的構(gòu)造函數(shù),我們可以發(fā)現(xiàn):實(shí)例屬性還是通過構(gòu)造函數(shù)方式來(lái)添加的。
最后,我們來(lái)看看 _classCallCheck 方法,它其實(shí)是一層校驗(yàn),保證了我們的實(shí)例對(duì)象是特定的類型。
所以,綜上所述,ES6 中的 class 只是個(gè)語(yǔ)法糖,它本質(zhì)上還是用組合使用構(gòu)造函數(shù)模式創(chuàng)建自定義類型的,這也就是為什么我們要學(xué)上面那些知識(shí)的初衷。
感興趣的朋友還可以使用本站在線HTML/CSS/JavaScript代碼運(yùn)行工具:http://tools.jb51.net/code/HtmlJsRun測(cè)試上述代碼運(yùn)行結(jié)果。
更多關(guān)于JavaScript相關(guān)內(nèi)容還可查看本站專題:《javascript面向?qū)ο笕腴T教程》、《JavaScript錯(cuò)誤與調(diào)試技巧總結(jié)》、《JavaScript數(shù)據(jù)結(jié)構(gòu)與算法技巧總結(jié)》、《JavaScript遍歷算法與技巧總結(jié)》及《JavaScript數(shù)學(xué)運(yùn)算用法總結(jié)》
希望本文所述對(duì)大家JavaScript程序設(shè)計(jì)有所幫助。
網(wǎng)站名稱:JS中創(chuàng)建自定義類型的常用模式總結(jié)【工廠模式,構(gòu)造函數(shù)模式,原型模式,動(dòng)態(tài)原型模式等】
分享URL:http://muchs.cn/article18/ijsdgp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站維護(hù)、軟件開發(fā)、網(wǎng)站營(yíng)銷、網(wǎng)站設(shè)計(jì)、關(guān)鍵詞優(yōu)化、移動(dòng)網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)