就好像用戶看到的都是由dom表現出來的,所有的業務處理都是在Page對象中處理的。如果業務越簡單,創建的Page對象數量就會越少;如果業務越復雜,那么相對而言Page對象數量就越多(或Page實例對象就會越復雜)。 Page對象主要做以下事情:
無論是在哪個時候,這兩點確實是前端開發的重中之重,換一句話說這就是前端核心開發內容。
為了讓Page對象更加專注于上面所提的兩件事情,將處理業務的細節轉移到復雜services的文件中,讓它成為數據樞紐,安排數據的走向,弄清楚數據到底是渲染到頁面上還是保存起來,從而做進一步的前后端數據交互。另一方面,把復雜的渲染部分,封裝成組件,將渲染邏輯以及數據渲染頁面的邏輯交給組件內部處理,減少Page代碼量,讓業務處理更加清晰。
我們在Page對dom進行原子性的操作,而不是另外抽出一層作為單獨渲染層。從分離上看似更加合理,然而在日常開發中,瀏覽器對于dom的處理已經夠全面了,大部分是可以通過一句代碼來實現的。對于復雜點的,通過簡單的封裝或者組件的處理就能實現(組件對dom也是直接操作)。在Page對象中處理代碼也不會太多,如果為了封裝而失去修改的方便性,其實是得不償失的,而且不同層之間的交互,會讓代碼更加的難以理解(因為dom也算是一層渲染層,額外加一次就顯得比較多余),這也是為什么堅持使用最原始的html的原因之一。
通過上面的分析,我們更傾向于把Page對象的主要任務作為數據的樞紐,負責數據的運輸,把數據讓給渲染層顯示,或將數據做處理保存,或將數據進行前后端數據交互等事情, 相當于MVC架構中的Controller部分,html渲染出來的dom層代表著View層,Page對象實際上沒有保存長期數據的習慣,如果需要長期保存的數據,可以把它放在App對象中,或者把它放在services的某個文件中。
因為頁面和history有很大的關系,并且當前顯示的頁面必須顯示在瀏覽器端的標簽欄中,秉承著異步按需加載的特點,將頁面的配置項固定設置為 { title: “頁面標題”, url: “頁面url”, js: “頁面的定義js文件”, name: “頁面的名稱” }對于Page對象,我希望能滿足以下情況:
這里要特別注意,在異步操作中,有時候頁面切換的時候,回調函數中處理dom的時候,會因為dom已被銷毀而出現錯誤;因此我們對頁面切換會對所有該頁面發起的ajax做中斷處理。在別的異步操作中,要確保異步操作完成后,再做頁面切換工作。
每個Page對象從加載到銷毀, 定義為一個生命周期,過程如下,用圖表示:
我們創建一個Page對象
function Page(name, title, url) { BaseProto.call(this); // 繼承自定義事件能力 this.domList = { }; // 緩存dom this.eventList = { }; // 緩存事件和dom的關系 this.parent = null; // 現在指App對象 this.parentDom = null; // 指向Page對象放置的dom this.template = document.createElement("template"); this.http = new Http(this); // 用于AJAX交互,后續介紹 this.data = { }; // 放置私有對象}Page.prototype = Object.create(BaseProto.prototype, { // 實例化的Page對象必須要重寫這兩個方法, 對應的步驟2 render: function (next) { throw new Error("render方法必須繼承重寫") }, // 使用attachDom和attachEvent用來緩存dom和緩存事件 getDomObj: function (next) { throw new Error("getDomObj方法必須繼承重寫") }, // render方法獲取html后,將html放在dom里面,bk代表初始化后的回調調用,對應步驟2, 3, 4 initialize: function (dom, html, bk) { this.template.innerHTML = html; var fragment = this.template.content; this.getDomObj(); dom.appendChild(fragment); this._beforeInit(bk); }, _beforeInit: function (next) { var that = this; if (typeof this.beforeInit === "function") { this.beforeInit(function () { that._init.apply(that, arguments); // 可以傳參 if (typeof next === "function") next(); }) } else { this._init(); if (typeof next === "function") next(); } }, // 步驟5,6交給用戶處理 _init: function () { this._addEventListeners(); // 綁定事件 if (typeof this.init === "function") this.init.apply(this, arguments); // 開始處理業務 }, // 代表銷毀對象,對應步驟7, 8 destroy: function () { // 清除外部引用 if (typeof this.dispose === "function") this.dispose(); this._removeEventListeners(); // 事件移除 this.eventDispatcher.destroy(); // 自定義事件銷毀 this.eventList.length = 0; // 事件緩存清除 this._removeDom(); // 移除dom this.template = null; this.parent = null; this.data = { }; this.parentDom = null; this.http.destroy(); // 銷毀對象,阻止未結束的請求。 }, // 緩存dom attachDom: function (cssQuery, key) { this.domList[key] = this.template.content.querySelector(cssQuery); return this; }, // 緩存事件 attachEvent: function (key, eventStr, fn, passive, doFn) { passive = passive || false; var eventList = this.eventList; doFn = doFn || fn.bind(this); // 獲取對應key的dom綁定事件數組描述對象 var eventObj = getEvent(eventList, { key: key }); if (eventObj) { var eventArray = eventObj.eventArray; // 找到該事件的綁定方法數組 var methodEventObj = getEvent(eventArray, { method: eventStr }); if (methodEventObj) { var fnArray = methodEventObj.fnArray; // 是否已經綁定,防止重復 var obj = getEvent(fnArray, { backFn: fn, passive: passive }); if (!obj) fnArray.push({ backFn: fn, passive: passive, doFn: doFn }); } else { eventArray.push({ method: eventStr, fnArray: [{ backFn: fn, passive: passive, doFn: doFn }] }) } } else { eventList.push({ key: key, eventArray: [{ method: eventStr, fnArray: [{ backFn: fn, passive: passive, doFn: doFn }] }] }) } return this; }, // 剩下的就是_addEventListeners,_removeEventListeners就是解析eventList的數據格式 // 綁定事件和移除事件, 過程略過});
下面來實現AJAX實現細節,代碼如下
function Http(target) { this.target = target; this.list = [];}Http.prototype = { constructor: Http, // ajax方法 ajax: function (option, bk) { var list = this.list, target = this.target, useType = false, commonHeader = Http.commonHeader; var xhr = new XMLHttpRequest(); list.push(xhr); if (option.username) xhr.open(option.method, option.url, option.async, option.username, option.password); else xhr.open(option.method, option.url, option.async); var header = option.header || { }; // 作為公共header for (var key in commonHeader) { xhr.setRequestHeader(key, commonHeader[key]) } // 特殊化header for (var i in header) { if (header["Content-Type"] !== "multipart/form-data") { xhr.setRequestHeader(i, header[i]); } } // 配置二進制流請求 if ("type" in option) { xhr.responseType = option.type; useType = true; } xhr.onload = function () { target.dispatchEvent("xhrload", { xhr: xhr }); if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) { var index = list.indexOf(xhr); list.splice(index, 1); var result = useType ? xhr.response || xhr.responseText : xhr.responseText; option.success.call(option.target, result); if (typeof bk === "function") bk(result); } else { if (typeof option.error === "function") option.error.call(target, xhr); } if (typeof option.complete === "function") option.complete.call(target, xhr); target.dispatchEvent("xhrcomplete", { xhr: xhr }); }; xhr.onerror = function () { target.dispatchEvent("xhrerror", { xhr: xhr }); if (typeof option.error === "function") option.error.call(target, xhr); if (typeof option.complete === "function") option.complete.call(target, xhr); target.dispatchEvent("xhrcomplete", { xhr: xhr }); }; if ("onabort" in option) { xhr.onabort = option.onabort; } var data = null; // 默認以url-encode if (option.data) { if ( header["Content-Type"] == "application/json") data = JSON.stringify(option.data); else if (header["Content-Type"] == "multipart/form-data") { data = new FormData(); for (var key in option.data) { data.append(key, option.data[key]); } } else data = serialize(option.data); } xhr.send(data); target.dispatchEvent("xhrstart", { xhr: xhr }); return xhr; }, // 銷毀 destroy: function () { var list = this.list; for (var i = list.length - 1; i >= 0; i--) { // 中斷請求,防止切換頁面導致回調函數中操作dom造成錯誤 list[i].abort(); list.splice(i, 1); } }};
接下來在Page的原型對象中加入post,get方法。
post: function (url, data, fn) { var obj = createRequest(this, url, data, feeback, option, "POST"); this.http.ajax(obj);},get: function (url, fn) { var obj = createRequest(this, url, undefined, feeback, option, "GET"); this.http.ajax(obj);},
公共函數,創建請求參數的統一方式。
function createRequest(target, url, data, feeback, option, method) { option = option || { }; option.header = option.header || { }; option.header["x-request-with"] = "XMLHttpRequest"; if (!("Content-Type" in option.header)) option.header["Content-Type"] = "application/json"; option.method = method || "GET"; option.success = feeback.bind(target); if ("onabort" in option) option.onabort = option.onabort.bind(target); option.target = target; option.url = url; option.data = data; option.async = typeof option.async === "undefined" ? true : option.async; return option;}
[案例地址]http://www.renxuan.tech:2005
主要對Page對象的用途做了簡要的介紹,以及它的生命周期,并且著重對ajax做了簡要的封裝, 下一章針對Page與history的綜合 應用進行介紹。
底層框架開源地址:https://gitee.com/string-for-100w/string
157789.html
本文由 貴州做網站公司 整理發布,部分圖文來源于互聯網,如有侵權,請聯系我們刪除,謝謝!
網絡推廣與網站優化公司(網絡優化與推廣專家)作為數字營銷領域的核心服務提供方,其價值在于通過技術手段與策略規劃幫助企業提升線上曝光度、用戶轉化率及品牌影響力。這...
在當今數字化時代,公司網站已成為企業展示形象、傳遞信息和開展業務的重要平臺。然而,對于許多公司來說,網站建設的價格是一個關鍵考量因素。本文將圍繞“公司網站建設價...
在當今的數字化時代,企業網站已成為企業展示形象、吸引客戶和開展業務的重要平臺。然而,對于許多中小企業來說,高昂的網站建設費用可能會成為其發展的瓶頸。幸運的是,隨...
首創輪胎的優點和缺點?首創輪胎,北京首創輪胎旗艦品牌。綜合經濟實力在輪胎行業排名第15位,半鋼子午線輪胎前5名。產品注冊商標為BCT、經綸、盾、奧特佳等。在輪胎行業可以算是(半鋼子午線輪胎)平均水平。優點:環保,剎車距離短,穩定性好。缺點:噪音大,帶水路面操控差。bct輪胎官網?BCT是北京第一輪胎,全稱是北京車輪??梢哉f經綸輪胎是低端輪胎中的佼佼者,尤其是這款輪胎質量非常好。北京輪輪胎以高強度橡...
主題WB包什么意思?WB是WindowBlinds5的縮寫。WindowBlinds5是一個系統主題改變軟件。不同于常見的XP系統主題,WindowBlinds5WindowBlinds是一款美化Windows系統的特殊應用,尤其是可以改變所有Windows應用窗口,安裝運行非常簡單。WindowBlinds安裝后可以集成到Windows操作系統中。而且這款軟件是微軟唯一授權的第三方系統美化軟件。...
成都到重慶的火車經過哪些地方?;1.如果從成都坐高鐵到重慶,高鐵經過璧山、永川、大足、榮昌、隆昌、內江、資陽,到達成都。2.景點大概是重慶的區縣,永川,大足,榮昌,永川茶山竹海,樂和樂都,大足石雕,龍水湖,榮昌路孔古鎮。資陽沒什么特別的。隆昌的石牌坊也被稱為牌坊之鄉。3.如果是高鐵,從成都到重慶,要走大成鐵路,金堂,遂寧然后向南轉到重慶。4.如果是普通列車,走成渝鐵路,簡陽,資陽,內江,隆昌,最后...