6.Page对象详解

6.Page对象详解序就好像用户看到的都是由dom表现出来的,所有的业务处理都是在Page对象中处理的。如果业务越简单,创建的Page对象数量就会越少;如果业务越复杂,那么相对而言Page对象数量就越多(或Page实例对象就会越复杂)。Page对象主要做以下事情:渲染页面,保证dom元素的合理安排,以及事件的处理;数据交互,将数据正确的放在dom元素中,并进行合理的前后端数据交互。无论是在哪个时候,这两点确实是前端开发的重中之重,换一句话说这就是前端核心开发内容。为了让Page对象更加专注于上面所提的两件事情,

大家好,又见面了,我是你们的朋友全栈君。

就好像用户看到的都是由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 = { 
}; // 缓存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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/157790.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 数列极限定义详解「建议收藏」

    数列极限定义详解「建议收藏」数列极限的定义个人感觉不太好理解,我看了两天的教程才彻底明白数列极限的定义。在一般的教材里数列极限的定义为这说的比较抽象,我半天都理解不了N是怎么来的,n与N有什么区别,为什么要用去减a,这里我对自己的疑问做了个总结。首先是为什么要用去减a,两个数之间的接近程度可以用两个之差的绝对值来度量,差值越小,两个数就越接近(说法来源于《高等数学》同济大学)。两个数的接近程度小于任给的正数​…

  • 只通过com.alibaba.fastjson.JSONArray实现okHttp下String转换JSONArray

    只通过com.alibaba.fastjson.JSONArray实现okHttp下String转换JSONArray我的Android不能导入常见的那六个包,会严重报错。我改了很久很久还是不能解决错误,也就不能使用net.sf包中的JSONArray直接使用newJSONArray(str)。给像我一样不能导入包的同学介绍一种方法importcom.alibaba.fastjson.JSONArray;importcom.alibaba.fastjson.JSONObject;Stri…

  • 大数据:数据采集平台之Apache Flume

    大数据:数据采集平台之Apache Flume大数据:数据采集平台之ApacheFlume官网:https://flume.apache.org/Flume是Apache旗下的一款开源、高可靠、高扩展、容易管理、支持客户扩展的数据采集系统。Flume使用JRuby来构建,所以依赖Java运行环境。Flume最初是由Cloudera的工程师设计用于合并日志数据的系统,后来逐渐发展用于处理流数据事件。Flume设计成一个分布式…

  • java最新漏洞_JavaMelody XXE漏洞(CVE-2018-15531)分析

    java最新漏洞_JavaMelody XXE漏洞(CVE-2018-15531)分析0x01背景JavaMelody是一款在生产和QA环境中对JAVA应用以及应用服务器(Tomcat、Jboss、Weblogic)进行监控的工具,可以通过图表给出监控数据,方便研发运维等找出响应瓶颈、优化响应等。近日发布了1.74.0版本,修复了一个XXE漏洞,漏洞编号CVE-2018-15531。攻击者利用漏洞,可以读取JavaMelody服务器上的敏感信息。0x02漏洞分析漏洞修复的com…

  • pycharm如何创建py文件_pycharm输入不了

    pycharm如何创建py文件_pycharm输入不了PyCharm是一款很好用的编写Python工程的IDE,用PyCharm创建一个Python文件或者向工程添加一个.py文件时,为了更好的使所编写的代码在各操作环境更好的运行,我们往往需要在.py文件中添加头文件标注相关信息。例如:打开PyCharm程序,根据菜单栏中按照如下进入设置:File->settings->Editor->FileandCodeTem…

  • nginx重启几种方法

    nginx重启几种方法

    2021年10月14日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号