发布时间:2025-12-09 11:55:16 浏览次数:1
就好像用户看到的都是由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