JS中創(chuàng)建自定義類型的常用模式總結(jié)【工廠模式,構(gòu)造函數(shù)模式,原型模式,動(dòng)態(tài)原型模式等】

本文實(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)

外貿(mào)網(wǎng)站建設(shè)