0%

小说章节合并插件-第四篇

合并小说插件第四篇

这次我们从后台走向前台了! 开始在网页里插入我们的页面元素, 刚开始我们加入一个按钮和一个表格, 感觉有点大张旗鼓, 哈哈哈。 。

本来我想着把所有的工作都用promise来实现, 但是这条路走入死胡同, 非常顺利的失败了。

我们知道获取第一页的内容后才能获取第二页的地址, 而且有多少页这是不确定的, 如果你强行使用promise实现难度太大, 我尝试了一下后来放弃了, 因为我们有更好的选择, 没必要在一棵树上吊死!

所以说在编写程序的时候遇到一些不能处理的问题可以改变一下思维, 看看有没有更简单的方法, 如果有的话果断改变, 如果没有继续思考其他办法, 不管编程还是和人交往不能过于死心眼, 这个好听叫执着, 难听就是杠精。

那么这个新的办法就是事件, 只要你用过html和js肯定遇到过事件, 比如按钮被单机或者文本框里输入内容后都会发生对应的事件, 我们可以编写事件处理函数来处理这些事件,

比如某个按钮被单机后发送一个网络请求, 又比如文本框里输入内容后检查这些内容是不是合法的等等等等, 只要遇到GUI程序的话, 事件肯定是最为常见的设计模式。

那么我们的后台脚本如何触发事件, 这些事件又是怎么处理?

这就是我们这一篇要重点关注的内容了!

我们知道已经有了很多标准的事件, 比如onclick; onload; onchange; onkeydown; keyup等等, 那么我们怎么生成自己的事件呢?

首先要有一个事件类型。

自定义事件的要点

我们知道Error类型表示一个错误, 那么Event类型表示一个事件, CustomEvent表示一个携带数据的事件。

// 最简单的事件
let event = new Event("name");
// 携带数据的事件
let customEvent = new CustomEvent("name", {detail: 任何数据});

那么如何触发事件, 如何处理事件呢?

在我们的DOM对象上有三个重要方法,

  • addEventListener(name, handler); 在当前对象添加一个事件

  • removeEventListener(name) 从当前对象移除一个事件

  • dispatchEvent(new Event(name)) 在当前对象下触发一个事件, 如果携带数据的话用CustomEvent类型

    一个显而易见的要点是, 必须调用addEventListener添加一个事件后才能用dispatchEvent来触发该事件, 如果事件不能触发的话看看两者的调用顺序。

    大家可以想一想一个DOM对象有各种属性方法和事件, 那么这些DOM对象是如何生成的。

    这个话题超出我们的主题了, 不过大家有空可以探索一下。

在项目里引入事件

首先我们修改novel函数的名称为Novel, 同时在main函数里同时修改它的调用方式, 原来是普通函数调用现在改为创建对象。

...
new Novel();
...

然后重新实现Novel函数, 不对现在应该称之为Novel对象。

function Novel()
{
this.chapters = new Array(); // 存放每个章节的标题和url
this.main = document.createElement("div"); // 作为我们插件的主要节点插入到当前页面的DOM里, 而且所有的事件都绑定在了这个元素上
this.table = document.createElement("table"); // 展示小说章节标题和URL

// 初始化代码块, 放到末尾调用
this.init = () =>
{
let downloadButton = document.createElement("button");
downloadButton.innerHTML = "下载全本";
downloadButton.addEventListener("click", () => alert(this.chapters.length)); // 对下载按钮添加一个事件处理函数这里没有on前缀
// 把按钮和表格加入到主节点, 然后把主节点加入到当前页面的body节点上
this.main.appendChild(downloadButton);
this.main.appendChild(this.table);
document.body.appendChild(this.main);
downloadButton.focus();

// 触发novelChapterPage事件, 而且传递小说章节列表的第一页的URL
this.main.dispatchEvent(new CustomEvent("novelChapterPage", {detail: getTextURL(document, "ablum_read", "正序")}));
}

// 处理novelChapterPage事件
this.main.addEventListener("novelChapterPage", (e) =>
{
download(e.detail)
.then((htmlDoc) =>
{
let dom = document.createElement("div");
dom.innerHTML = htmlDoc;
// 迭代所有的章节
for (let el of dom.getElementsByClassName("chapter")[0].getElementsByTagName("a"))
{
let title = el.innerText;
let url = el.getAttribute("href");
// 把章节标题和URL加入到表格里
let row = this.table.insertRow();
row.insertCell().innerText = title;
row.insertCell().innerText = url;

this.chapters.push({title, url});
}
let url = getTextURL(dom, "page", "下一页");
// 如果有下一页继续触发novelChapterPage事件, 相当于自己调用自己
if (url)
{
this.main.dispatchEvent(new CustomEvent("novelChapterPage", {detail: url}));
}
})
.catch(alert);
});

this.init();
}

运行后, 快速的点击下载按钮, 看看什么场景, 然后在看看生成的表格的内容, 发现一个非常严重的问题, 章节缺失, 至少缺失了10%的章节,

如果你比较细心观察html应该早就发现了问题所在, 实际上少部分章节没有提供URL, 只是展示了章节名称而已,

这个问题怎么解决, 我们发现每个章节的序号没有规律可言, 所以首先获取章节实际内容的时候需要额外的保存下一张的URL, 然后检测到章节缺失后在调用获取内容的函数, 这个先放一放。

获取章节内容是我们下一次主要解决的问题了, 到时候在一起解决章节缺失的问题。。

最后总结一下事件, 其实事件很简单, 需要注意的是。

触发事件和处理事件的先后顺序需要格外的留意

不要硬编码名称, 比如我们上面的例子很有问题正确做法是

const EVT_NOVEL_CHAPTER_PAGE = "novelChapterPage";
...
obj.addEventListener(EVT_NOVEL_CHAPTER_PAGE, handler);
obj.dispatchEvent(new Event(EVT_NOVEL_CHAPTER_PAGE));

其实上面的原则适用于任何程序的任何标识符, 可以考虑如下代码。

dispatchEvent(new Event("novelchapterpage")); // 运行出错, 用了大量的时间和经历找到大小写的问题, 
dispatchEvent(new Event(EVT_NOVEL_CHAPTERPAGE)); // 运行的时候直接报错, EVT_NOVEL_CHAPTERPAGE not fount, 马上看到少了一个下划线不到一分钟就搞定问题

如果这是一个小小脚本怎么写都问题不大, 如果这是一个大型项目呢? 几十行几百行问题都不大, 几千行几万行以后问题就大了。

所以某个字面量如果被用到多次的可能, 那么果断定义一个常量。

本篇到此为止, 下一次我们讲一讲如何获取小说章节实际内容。

未完待续。