6.Page对象详解

article/2025/9/21 8:35:45

就好像用户看到的都是由dom表现出来的,所有的业务处理都是在Page对象中处理的。如果业务越简单,创建的Page对象数量就会越少;如果业务越复杂,那么相对而言Page对象数量就越多(或Page实例对象就会越复杂)。
Page对象主要做以下事情:

  1. 渲染页面,保证dom元素的合理安排,以及事件的处理;
  2. 数据交互,将数据正确的放在dom元素中,并进行合理的前后端数据交互。

无论是在哪个时候,这两点确实是前端开发的重中之重,换一句话说这就是前端核心开发内容。

为了让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对象,我希望能满足以下情况:

  1. 它具有一般模块的方法,还应该拥有对渲染层事件绑定等处理;
  2. 拥有传统的方式,将dom缓存起来,下次使用获取时加快速度,拥有事件管理能力(不然页面切换无法解绑dom事件);
  3. 只有它拥有与后端交互的能力,仅有Page对象才拥有与后端交互的所有能力。前后端交互是业务的核心之一。

这里要特别注意,在异步操作中,有时候页面切换的时候,回调函数中处理dom的时候,会因为dom已被销毁而出现错误;因此我们对页面切换会对所有该页面发起的ajax做中断处理。在别的异步操作中,要确保异步操作完成后,再做页面切换工作。

实现思路

每个Page对象从加载到销毁, 定义为一个生命周期,过程如下,用图表示:
生命周期

  1. 获取Page实例对象的js,加载js;
  2. 调用render方法,将html获取到加载到某个dom中,这里处理的方式是放在fragment中;
  3. 接着调用getDomObj方法,目的就是缓存dom,并且绑定事件;
  4. 将fragment加载到浏览器的dom中,展示页面,如果存在beforeInit方法,先执行beforeInit方法;
  5. 调用init方法,初始化该页面需要引入的插件;
  6. 日常的业务处理,等待用户切换页面;
  7. 首先调用dispose方法,这个方法主要是处理引入的插件的销毁;
  8. 销毁,主要是移除dom事件的注册,移除dom的引用,删除临时数据, 与后端ajax交互中断等。

我们创建一个Page对象

function Page(name, title, url) {BaseProto.call(this); // 继承自定义事件能力this.domList = {}; // 缓存domthis.eventList = {}; // 缓存事件和dom的关系this.parent = null; // 现在指App对象this.parentDom = null; // 指向Page对象放置的domthis.template = document.createElement("template");this.http = new Http(this); // 用于AJAX交互,后续介绍this.data = {}; // 放置私有对象
}
Page.prototype = Object.create(BaseProto.prototype, {// 实例化的Page对象必须要重写这两个方法, 对应的步骤2render: function (next) {throw new Error("render方法必须继承重写")},// 使用attachDom和attachEvent用来缓存dom和缓存事件getDomObj: function (next) {throw new Error("getDomObj方法必须继承重写")},// render方法获取html后,将html放在dom里面,bk代表初始化后的回调调用,对应步骤2, 3, 4initialize: 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, 8destroy: function () {// 清除外部引用if (typeof this.dispose === "function") this.dispose();this._removeEventListeners(); // 事件移除this.eventDispatcher.destroy(); // 自定义事件销毁this.eventList.length = 0; // 事件缓存清除this._removeDom(); // 移除domthis.template = null;this.parent = null;this.data = {};this.parentDom = null;this.http.destroy(); // 销毁对象,阻止未结束的请求。},// 缓存domattachDom: 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);elsexhr.open(option.method, option.url, option.async);var header = option.header || {};// 作为公共headerfor (var key in commonHeader) {xhr.setRequestHeader(key, commonHeader[key])}// 特殊化headerfor (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-encodeif (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


http://chatgpt.dhexx.cn/article/w5YABPbn.shtml

相关文章

java中page的对象,page对象

page对象是JSP九大内置对象之一。JSP全称JavaServerPage&#xff0c;是一种动态网页技术标准&#xff0c;以Java语言作为脚本语言。在JSP中预先定义了九个内置对象,这个九个内置对象不需要声明就可以在脚本代码和表达式中任意使用&#xff0c;九个内置对象分别是&#xff1a;re…

Java程序员经典面试题+答案(全)

这套面试题主要目的是帮助那些还没有java软件开发实际工作经验&#xff0c;而正在努力寻找java软件开发工作的朋友在笔试时更好地赢得笔试和面试。 关注公众号【Java典籍】免费赠送一套Java入门视频教程一套&#xff01; 1、一个".java"源文件中是否可以包括多个类&a…

2023年Java最新面试题,附详解答案

写在前面&#xff1a;本篇面试题整理是我在广州地区进行面试整理得出&#xff0c;常见的基本都在下面了。基本都是可以直接在面试时用白话回答的答案总结&#xff0c;面试时照此回答即可&#xff0c;有其他常见面试问题也欢迎在评论区补充。 首次发布&#xff1a;202 3年 1月 更…

java初中级面试题集锦

目录 基础 &与&&区别 使用 final 关键字修饰一个变量时&#xff0c;是引用不能变&#xff0c;还是引用的对象不能变&#xff1f; 静态变量和实例变量的区别&#xff1f; 是否可以从一个 static 方法内部发出对非 static 方法的调用&#xff1f; ""…

Java程序员面试题(一)JavaSE基础 - 上

如果觉得文章对您有一点点帮助&#xff0c;麻烦帮忙点个赞?哦 目录 一、Java面向对象1. 面向对象都有哪些特性以及你对这些特性的理解2.访问权限修饰符 public、private、protected, 以及不写&#xff08;默认&#xff09;时的区别3.如何理解 clone 对象3.1 为什么要用 clone…

Java面试题200+大全(合适各级Java人员)

前言&#xff1a; 本文整理所得&#xff01;但是内容是很干货的&#xff01;我看了也有帮助。做个分享。该列表包含了入门级 Java 程序员和多年经验的高级开发者的问题。无论你是 1、2、3、4、5、6、7、8、9 还是 10 年经验的开发者&#xff0c;你都能在其中找到一些有趣的问题…

Java 程序员不得不会的 124 道面试题(含答案)

多线程、并发及线程的基础问题 1&#xff09;Java 中能创建 volatile 数组吗&#xff1f; 能&#xff0c;Java 中可以创建 volatile 类型数组&#xff0c;不过只是一个指向数组的引用&#xff0c;而不是整个数组。我的意思是&#xff0c;如果改变引用指向的数组&#xff0c;将…

java程序员面试常见面试题及答案整理

JAVA相关基础知识   1、面向对象的特征有哪些方面   1.抽象:   抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节。抽象包括两个方面,一是过程抽象,二是…

2020年高级Java程序员面试题汇总,含答案总结

文章目录 JDKDubboZookeeperSpring系列Redis系列Mysql系列Java多线程消息中间件线程池JVM设计模式其他程序设计基础知识算法题面试技术外的问题面试答案引言: 随着金九银十的面试跳槽,回首在经历了两周的跳槽求职路后,终于成功拿到offer,距今已经入职快一个月了,因此,这几…

2018java程序员面试题整理

1.PathVariable注解和RequestParam注解的区别。 RequestParam注解是获取静态URL传入的参数 PathVariable是获取请求路径中的变量作为参数 /需要和RequestMapping("item/{itemId}") 配合使用 2.Param注解和RequestParam注解的区别。 Parm 指定request中必须包含某…

Java程序员不得不会的面试题(含答案)

每个技术人都有个大厂梦&#xff0c;我觉得这很正常&#xff0c;并不是饭后的谈资而是每个技术人的追求。像阿里、腾讯、美团、字节跳动、京东等等的技术氛围与技术规范度还是要明显优于一些创业型公司/小公司&#xff0c;如果说能够在这样的公司锻炼几年&#xff0c;相信对自己…

Java程序员面试题集(1-50)

一、Java基础部分 1、面向对象的特征有哪些方面? 答&#xff1a;面向对象的特征主要有以下几个方面&#xff1a; 1)抽象&#xff1a;抽象是将一类对象的共同特征总结出来构造类的过程&#xff0c;包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为&#xff0c…

Java程序员面试题

下面的内容是对网上原有的Java面试题集及答案进行了全面修订之后给出的负责任的题目和答案&#xff0c;原来的题目中有很多重复题目和无价值的题目&#xff0c;还有不少的参考答案也是错误的&#xff0c;修改后的Java面试题集参照了JDK最新版本&#xff0c;去掉了EJB 2.x等无用…

Java程序员不得不会的124道面试题(含答案)

专注于编程、互联网动态。最终将总结的技术、心得、经验&#xff08;数据结构与算法、源码分析等&#xff09;享给大家&#xff0c;这里不只限于技术&#xff01;还有职场心得、生活感悟、以及面经。 点击上方 关注按钮&#xff0c;第一时间送达&#xff01; 多线程、并发及线…

Java常见面试题

Java面试题 java基础 spring & springMVC mybatis、mybatisplus springboot & springcloudAlibaba redis & mongodb & mysql rabbitmq & kafka docker & linux 一&#xff1a;java基础 1.1&#xff1a;java 面向对象三大特性&#xff08;封装…

Java程序员面试常见试题汇总

下面是Java程序员相关的热门面试题&#xff0c;你可以用它来好好准备面试。 什么是线程&#xff1f;   线程是操作系统能够进行运算调度的最小单位&#xff0c;它被包含在进程之中&#xff0c;是进程中的实际运作单位。程序员可以通过它进行多处理器编程&#xff0c;你可以使…

10万字208道Java经典面试题总结(附答案)

&#x1f345; 作者简介&#xff1a;哪吒&#xff0c;CSDN2021博客之星亚军&#x1f3c6;、新星计划导师✌、博客专家&#x1f4aa; &#x1f345; 哪吒多年工作总结&#xff1a;Java学习路线总结&#xff0c;搬砖工逆袭Java架构师 &#x1f345; 技术交流&#xff1a;定期更新…

2021最新社招面试真题385题,解决你所有面试不懂的问题【Java社招篇】

金九银十已经过去了很久&#xff0c;大家应该都在为明年的求职或者跳槽做准备了&#xff0c;现在给大家总结了一下今年社招都有哪些面试题&#xff0c;包含了全年最全的面试真题&#xff0c;所有面试真题的答案就不在这里一一给大家总结出来&#xff0c;有需要答案的同学&#…

Java实习生面试题(一)

面临金九银十的黄金跳槽季&#xff0c;不光是很多在职人员跃跃欲试&#xff0c;也有很多大学生积极地加入到应聘行列。作为技术出身的我们&#xff0c;找工作的一大前提就是技术是否能过关。而对于java程序员来说&#xff0c;至少是两关的面试&#xff0c;这首个关卡就是笔试&a…

阿里最新Java面试题,摘选最具代表性49题,看了答案也不过如此

本文转载自:阿里最新Java面试题,摘选最具代表性49题,看了答案也不过如此 不想当将军的士兵不是好士兵,不想成为架构师的Java程序员不是好程序员!为什么要成为架构师,为什么要进大厂? 那年十八 母校舞会 站着如喽啰 那时候 我含泪 发誓各位 必须看到我 1. 面向对象的特征…