最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

JavaScript中的狀態(tài)模式詳解

 更新時間:2024年11月25日 15:37:44   作者:橘貓吃不胖~  
狀態(tài)模式允許對象在其內(nèi)部狀態(tài)改變時改變其行為,通過將每種狀態(tài)封裝成單獨的類,狀態(tài)模式可以消除原本存在的大量條件分支語句,使得代碼更易讀和維護

1 什么是狀態(tài)模式

允許一個對象在其內(nèi)部狀態(tài)改變時改變它的行為,對象看起來似乎修改了它的類。

比如說這樣一個場景:

  • 有一個電燈,電燈上面只有一個開關。
  • 當電燈開著的時候,此時按下開關,電燈會切換到關閉狀態(tài);再按一次開關,電燈又將被打開。
  • 同一個開關按鈕,在不同的狀態(tài)下,表現(xiàn)出來的行為是不一樣的。

我們用代碼來描述上面的場景:

// 定義一個Light類
var Light = function () {
  this.state = "off"; // 給電燈設置初始狀態(tài) off
  this.button = null; // 電燈開關按鈕
};

// 在頁面中創(chuàng)建一個真實的button節(jié)點
Light.prototype.init = function () {
  var button = document.createElement("button"), // 創(chuàng)建一個開關按鈕
    self = this;
  button.innerHTML = "開關";
  this.button = document.body.appendChild(button);
  // 開關被按下的事件
  this.button.onclick = function () {
    self.buttonWasPressed();
  };
};

// 開關被按下的行為
Light.prototype.buttonWasPressed = function () {
  // 如果當前是關燈狀態(tài),按下開關表示開燈
  if (this.state === "off") {
    console.log("開燈");
    this.state = "on";
  } else if (this.state === "on") {
    // 如果當前是開燈狀態(tài),按下開關表示關燈
    console.log("關燈");
    this.state = "off";
  }
};

var light = new Light();
light.init();

但是燈的種類是多種多樣的,另外一種電燈,這種電燈也只有一個開關,但它的表現(xiàn)是:第一次按下打開弱光,第二次按下打開強光,第三次才是關閉電燈,現(xiàn)在我們改造上面的代碼來完成這種新型電燈的制造:

Light.prototype.buttonWasPressed = function () {
  if (this.state === "off") {
    console.log("弱光");
    this.state = "weakLight";
  } else if (this.state === "weakLight") {
    console.log("強光");
    this.state = "strongLight";
  } else if (this.state === "strongLight") {
    console.log("關燈");
    this.state = "off";
  }
};

在上面的代碼中,存在一些很明顯的缺點:

  • buttonWasPressed方法違反開放—封閉原則,每次新增或者修改燈光的狀態(tài),都需要改動buttonWasPressed方法中的代碼,這使其成為了一個非常不穩(wěn)定的方法
  • 所有跟狀態(tài)有關的行為,都被封裝在buttonWasPressed方法里,如果這個電燈又增加了其他光的種類,那這個方法會越來越龐大
  • 狀態(tài)的切換不明顯,僅僅表現(xiàn)為改變state,容易漏掉某些狀態(tài)
  • 狀態(tài)之間的切換關系,是靠if、else語句,增加或者修改一個狀態(tài)可能需要改變?nèi)舾蓚€操作,這使代碼難以閱讀和維護

2 使用狀態(tài)模式改造電燈程序

狀態(tài)模式的關鍵是把事物的每種狀態(tài)都封裝成單獨的類,跟此種狀態(tài)有關的行為都被封裝在這個類的內(nèi)部,所以button被按下的的時候,只需要在上下文中,把這個請求委托給當前的狀態(tài)對象即可,該狀態(tài)對象會負責渲染它自身的行為。

同時我們還可以把狀態(tài)的切換規(guī)則事先分布在狀態(tài)類中, 這樣就有效地消除了原本存在的

大量條件分支語句,代碼如下:

// OffLightState:
var OffLightState = function (light) {
  this.light = light;
};
OffLightState.prototype.buttonWasPressed = function () {
  console.log("弱光"); // offLightState 對應的行為
  this.light.setState(this.light.weakLightState); // 切換狀態(tài)到 weakLightState
};

// WeakLightState:
var WeakLightState = function (light) {
  this.light = light;
};
WeakLightState.prototype.buttonWasPressed = function () {
  console.log("強光"); // weakLightState 對應的行為
  this.light.setState(this.light.strongLightState); // 切換狀態(tài)到 strongLightState
};

// StrongLightState:
var StrongLightState = function (light) {
  this.light = light;
};
StrongLightState.prototype.buttonWasPressed = function () {
  console.log("關燈"); // strongLightState 對應的行為
  this.light.setState(this.light.offLightState); // 切換狀態(tài)到 offLightState
};

// 改寫Light類,在Light類中為每個狀態(tài)類都創(chuàng)建一個狀態(tài)對象,可以很明顯的看到燈的種類
var Light = function () {
  this.offLightState = new OffLightState(this);
  this.weakLightState = new WeakLightState(this);
  this.strongLightState = new StrongLightState(this);
  this.button = null;
};

// 按下按鈕的事件中,將請求委托給當前持有的狀態(tài)對象去執(zhí)行
Light.prototype.init = function () {
  var button = document.createElement("button"), // 創(chuàng)建button
    self = this;
  this.button = document.body.appendChild(button);
  this.button.innerHTML = "開關";
  // 設置當前狀態(tài)
  this.currState = this.offLightState;
  this.button.onclick = function () {
    self.currState.buttonWasPressed();
  };
};

// 切換light對象的狀態(tài)
Light.prototype.setState = function (newState) {
  this.currState = newState;
};

var light = new Light();
light.init();

3 缺少抽象類的變通方式

在上面的代碼中,在狀態(tài)類中將定義一些共同的行為方法,Context最終會將請求委托給狀態(tài)對象的這些方法,在這個例子里這個方法就是buttonWasPressed。無論增加了多少種狀態(tài)類,它們都必須實現(xiàn)buttonWasPressed方法。

所以使用狀態(tài)模式的時候要格外小心,如果我們編寫一個狀態(tài)子類時,忘記了給這個狀態(tài)子類實現(xiàn)buttonWasPressed方法,則會在狀態(tài)切換的時候拋出異常,因為Context總是把請求委托給狀態(tài)對象的buttonWasPressed方法。

因此我們讓抽象父類的抽象方法直接拋出一個異常:

var State = function () {};
State.prototype.buttonWasPressed = function () {
  throw new Error("父類的 buttonWasPressed 方法必須被重寫");
};
var SuperStrongLightState = function (light) {
  this.light = light;
};
SuperStrongLightState.prototype = new State(); // 繼承抽象父類
SuperStrongLightState.prototype.buttonWasPressed = function () {
  // 重寫 buttonWasPressed 方法
  console.log("關燈");
  this.light.setState(this.light.offLightState);
};

4 示例:文件上傳

4.1 場景描述

例如,控制文件上傳需要兩個節(jié)點按鈕,第一個用于暫停和繼續(xù)上傳,第二個用于刪除文件

  • 當文件在掃描狀態(tài)中,不能進行任何操作,既不能暫停也不能刪除文件,只能等待掃描完成。掃描完成之后,根據(jù)文件的md5值判斷,若確認該文件已經(jīng)存在于服務器,則直接跳到上傳完成狀態(tài)。如果該文件的大小超過允許上傳的最大值,或者該文件已經(jīng)損壞,則跳往上傳失敗狀態(tài)。剩下的情況下才進入上傳中狀態(tài)
  • 上傳過程中可以點擊暫停按鈕來暫停上傳,暫停后點擊同一個按鈕會繼續(xù)上傳
  • 掃描和上傳過程中,點擊刪除按鈕無效,只有在暫停、上傳完成、上傳失敗之后,才能刪除文件

假設我們使用一個插件對象幫助我們完成上傳工作:

var plugin = (function () {
  var plugin = document.createElement("embed");
  plugin.style.display = "none";
  plugin.type = "application/txftn-webkit";
  plugin.sign = function () {
    console.log("開始文件掃描");
  };
  plugin.pause = function () {
    console.log("暫停文件上傳");
  };
  plugin.uploading = function () {
    console.log("開始文件上傳");
  };
  plugin.del = function () {
    console.log("刪除文件上傳");
  };
  plugin.done = function () {
    console.log("文件上傳完成");
  };
  document.body.appendChild(plugin);
  return plugin;
})();

上傳是一個異步的過程,所以控件會不停地調(diào)用全局函數(shù)window.external.upload,來通知目前的上傳進度,控件會把當前的文件狀態(tài)作為參數(shù)state塞進window.external.upload,在此例中該函數(shù)負責打印一些log

window.external.upload = function (state) {
  console.log(state); // 可能為 sign、uploading、done、error
};

4.2 代碼過程

首先定義Upload類,在構造函數(shù)中為每種狀態(tài)子類都創(chuàng)建一個實例對象:

var Upload = function (fileName) {
  this.plugin = plugin;
  this.fileName = fileName;
  this.button1 = null;
  this.button2 = null;
  this.signState = new SignState(this); // 設置初始狀態(tài)為 waiting
  this.uploadingState = new UploadingState(); // 上傳中
  this.pauseState = new PauseState(this); // 暫停
  this.doneState = new DoneState(this); // 上傳完成
  this.errorState = new ErrorState(this); // 上傳錯誤
  this.currState = this.signState; // 設置當前狀態(tài)
};

創(chuàng)建兩個按鈕,一個控制文件暫停和繼續(xù)上傳,一個用于刪除文件:

Upload.prototype.init = function () {
  var that = this;
  this.dom = document.createElement("div");
  this.dom.innerHTML =
    "<span>文件名稱:" +
    this.fileName +
    '</span><button data-action="button1">掃描中</button><button data-action="button2">刪除</button>';
  document.body.appendChild(this.dom);
  this.button1 = this.dom.querySelector('[data-action="button1"]'); // 第一個按鈕
  this.button2 = this.dom.querySelector('[data-action="button2"]'); // 第二個按鈕
  this.bindEvent();
};

為兩個按鈕分別綁定點擊事件,在點擊了按鈕之后,Context并不做任何具體的操作,而是把請求委托給當前的狀態(tài)類來執(zhí)行:

Upload.prototype.bindEvent = function () {
  var self = this;
  this.button1.onclick = function () {
    self.currState.clickHandler1();
  };
  this.button2.onclick = function () {
    self.currState.clickHandler2();
  };
};

// 掃描中
Upload.prototype.sign = function () {
  this.plugin.sign();
  this.currState = this.signState;
};
// 上傳中
Upload.prototype.uploading = function () {
  this.button1.innerHTML = "正在上傳,點擊暫停";
  this.plugin.uploading();
  this.currState = this.uploadingState;
};
// 暫停
Upload.prototype.pause = function () {
  this.button1.innerHTML = "已暫停,點擊繼續(xù)上傳";
  this.plugin.pause();
  this.currState = this.pauseState;
};
// 上傳成功
Upload.prototype.done = function () {
  this.button1.innerHTML = "上傳完成";
  this.plugin.done();
  this.currState = this.doneState;
};
// 上傳失敗
Upload.prototype.error = function () {
  this.button1.innerHTML = "上傳失敗";
  this.currState = this.errorState;
};
// 刪除
Upload.prototype.del = function () {
  this.plugin.del();
  this.dom.parentNode.removeChild(this.dom);
};

再接下來是編寫各個狀態(tài)類的實現(xiàn):

var StateFactory = (function () {
  var State = function () {};
  State.prototype.clickHandler1 = function () {
    throw new Error("子類必須重寫父類的 clickHandler1 方法");
  };
  State.prototype.clickHandler2 = function () {
    throw new Error("子類必須重寫父類的 clickHandler2 方法");
  };
  return function (param) {
    var F = function (uploadObj) {
      this.uploadObj = uploadObj;
    };
    F.prototype = new State();
    for (var i in param) {
      F.prototype[i] = param[i];
    }
    return F;
  };
})();

var SignState = StateFactory({
  clickHandler1: function () {
    console.log("掃描中,點擊無效...");
  },
  clickHandler2: function () {
    console.log("文件正在上傳中,不能刪除");
  },
});

var UploadingState = StateFactory({
  clickHandler1: function () {
    this.uploadObj.pause();
  },
  clickHandler2: function () {
    console.log("文件正在上傳中,不能刪除");
  },
});

var PauseState = StateFactory({
  clickHandler1: function () {
    this.uploadObj.uploading();
  },
  clickHandler2: function () {
    this.uploadObj.del();
  },
});

var DoneState = StateFactory({
  clickHandler1: function () {
    console.log("文件已完成上傳, 點擊無效");
  },
  clickHandler2: function () {
    this.uploadObj.del();
  },
});

var ErrorState = StateFactory({
  clickHandler1: function () {
    console.log("文件上傳失敗, 點擊無效");
  },
  clickHandler2: function () {
    this.uploadObj.del();
  },
});

測試一下:

var uploadObj = new Upload("AAAAAAAAA");
uploadObj.init();
window.external.upload = function (state) {
  // 插件調(diào)用 JavaScript 的方法
  uploadObj[state]();
};
window.external.upload("sign"); // 文件開始掃描
setTimeout(function () {
  window.external.upload("uploading"); // 1 秒后開始上傳
}, 1000);
setTimeout(function () {
  window.external.upload("done"); // 5 秒后上傳完成
}, 5000);

總結

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關文章

  • Javascript實現(xiàn)滾動圖片新聞的實例代碼

    Javascript實現(xiàn)滾動圖片新聞的實例代碼

    這篇文章主要介紹了Javascript實現(xiàn)滾動圖片新聞的實例代碼。需要的朋友可以過來參考下,希望對大家有所幫助
    2013-11-11
  • addEventListener()與removeEventListener()解析

    addEventListener()與removeEventListener()解析

    這篇文章主要為大家詳細介紹了addEventListener()與removeEventListener(),用于處理指定和刪除事件處理程序操作,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-04-04
  • js AspxButton的客戶端操作

    js AspxButton的客戶端操作

    processOnServer使您可以指定當前Button應該處理客戶端的事件或服務器端事件。
    2009-06-06
  • javascript中節(jié)點的最近的相關節(jié)點訪問方法

    javascript中節(jié)點的最近的相關節(jié)點訪問方法

    parentNode——父節(jié)點;firstChild——第一個子節(jié)點;lastChild——最后一個子節(jié)點;previousSibling——緊挨著的前面的兄弟節(jié)點;這樣就可以作短途旅行,訪問當前節(jié)點的某些相關節(jié)點,感興趣的你可以參考下哈
    2013-03-03
  • 手把手教你寫一個微信小程序(推薦)

    手把手教你寫一個微信小程序(推薦)

    最近接了一個微信小程序的項目,項目需求是小程序語音識別,全景圖片觀看,登錄授權,獲取個人基本信息。非常不錯,具有一定的參考借鑒價值,需要的朋友參考下吧
    2018-10-10
  • javascript實現(xiàn)校驗文件上傳控件實例

    javascript實現(xiàn)校驗文件上傳控件實例

    這篇文章主要介紹了javascript實現(xiàn)校驗文件上傳控件,實例分析了javascript檢測上傳文件類型是否為圖片的功能,非常具有實用價值,需要的朋友可以參考下
    2015-04-04
  • 詳解js創(chuàng)建對象的幾種方法及繼承

    詳解js創(chuàng)建對象的幾種方法及繼承

    這篇文章主要介紹了js創(chuàng)建對象的幾種方法及繼承,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-04-04
  • 前端try?catch的核心目的與應用場景示例詳解

    前端try?catch的核心目的與應用場景示例詳解

    在Vue中處理異常的一種簡單方法是使用JavaScript中的try-catch語句,這篇文章主要介紹了前端try?catch的核心目的與應用場景的相關資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2026-02-02
  • JavaScript中的邏輯判斷符&&、||與!介紹

    JavaScript中的邏輯判斷符&&、||與!介紹

    這篇文章主要介紹了JavaScript中的邏輯判斷符&&、||與!介紹,本文講解了邏輯與&&的處理規(guī)則、邏輯或||的處理規(guī)則、!操作符,需要的朋友可以參考下
    2014-12-12
  • js contains方法實現(xiàn)代碼

    js contains方法實現(xiàn)代碼

    IE有許多好用的方法,后來都被其他瀏覽器抄襲了,比如這個contains方法。如果A元素包含B元素,則返回true,否則false。唯一不支持這個方法的是IE的死對頭firefox。
    2011-01-01

最新評論

开封县| 哈密市| 宾阳县| 濮阳市| 开封市| 易门县| 浦东新区| 鄂温| 宁德市| 江川县| 离岛区| 桦南县| 漳浦县| 屏东县| 庆安县| 林口县| 井冈山市| 龙里县| 景谷| 湖州市| 衢州市| 深泽县| 二连浩特市| 达拉特旗| 分宜县| 留坝县| 义马市| 金华市| 罗田县| 泗洪县| 汕尾市| 靖安县| 蒙自县| 翁源县| 修武县| 夏河县| 忻城县| 锦州市| 宾阳县| 张家界市| 任丘市|