最佳实践
书源开发中常见的模式、技巧和注意事项。
URL 处理
相对 URL 转绝对 URL
js
var BASE = 'https://www.example.com';
function fullUrl(path) {
if (!path) return '';
if (path.indexOf('http') === 0) return path;
if (path.indexOf('//') === 0) return 'https:' + path;
if (path.indexOf('/') === 0) return BASE + path;
return BASE + '/' + path;
}封面 URL 兼容
部分站点封面使用相对路径、协议相对路径或 Base64 data URI:
js
function fixCover(src) {
if (!src) return '';
if (src.indexOf('data:') === 0) return src; // Base64 直接返回
if (src.indexOf('//') === 0) return 'https:' + src; // 协议相对
if (src.indexOf('http') !== 0) return BASE + src; // 相对路径
return src;
}噪声过滤
DOM 广告节点移除
js
function cleanContent(html) {
var doc = legado.dom.parse(html);
// 移除常见广告容器
var adSelectors = ['.ad', '.ads', '.adsbygoogle', '#ad', '[class*="sponsor"]'];
for (var i = 0; i < adSelectors.length; i++) {
legado.dom.remove(doc, adSelectors[i]);
}
return legado.dom.text(doc, '#content');
}文本噪声正则过滤
js
function cleanText(text) {
return text
.replace(/本章未完.*?点击下一页继续/g, '')
.replace(/手机用户请浏览.*?阅读/g, '')
.replace(/天才一秒记住.*?地址/g, '')
.replace(/\s{3,}/g, '\n\n') // 合并多余空行
.trim();
}编码处理
GBK 站点
js
function search(keyword, page) {
var encoded = legado.encoding.encode(keyword, 'gbk');
var url = BASE + '/search.php?keyword=' + encoded;
// 指定 GBK 编码获取
var html = legado.http.get(url, { 'Accept-Charset': 'gbk' });
// ...
}分页处理
搜索翻页
js
function search(keyword, page) {
var url = BASE + '/search?q=' + encodeURIComponent(keyword) + '&page=' + page;
var html = legado.http.get(url);
var doc = legado.dom.parse(html);
var items = legado.dom.select(doc, '.book-item');
if (items.length === 0) return []; // 空页 = 终止翻页
return items.map(function(el) {
return {
name: legado.dom.text(el, '.title'),
author: legado.dom.text(el, '.author'),
bookUrl: fullUrl(legado.dom.attr(el, 'a', 'href')),
coverUrl: fixCover(legado.dom.attr(el, 'img', 'src'))
};
});
}章节列表分页
js
function chapterList(tocUrl) {
var chapters = [];
var html = legado.http.get(tocUrl);
var doc = legado.dom.parse(html);
// 检测分页
var pages = legado.dom.select(doc, '.page-select option');
if (pages.length > 1) {
var urls = pages.map(function(opt) {
return fullUrl(legado.dom.attr(opt, null, 'value'));
});
var htmls = legado.http.batchGet(urls);
for (var i = 0; i < htmls.length; i++) {
chapters = chapters.concat(parseChapters(legado.dom.parse(htmls[i])));
}
} else {
chapters = parseChapters(doc);
}
return chapters;
}错误处理
网络请求重试
js
function httpGetRetry(url, retries) {
retries = retries || 2;
for (var i = 0; i <= retries; i++) {
try {
return legado.http.get(url);
} catch (e) {
if (i === retries) throw e;
legado.log('[retry] 第 ' + (i + 1) + ' 次重试: ' + url);
}
}
}优雅降级
js
function bookInfo(bookUrl) {
var html = legado.http.get(bookUrl);
var doc = legado.dom.parse(html);
return {
name: legado.dom.text(doc, 'h1.title') || legado.dom.text(doc, 'h1') || '未知',
author: legado.dom.text(doc, '.author') || legado.dom.text(doc, 'meta[name="author"]@content') || '',
intro: legado.dom.text(doc, '.intro') || legado.dom.text(doc, '.description') || '',
coverUrl: fixCover(
legado.dom.attr(doc, '.cover img', 'src') ||
legado.dom.attr(doc, 'meta[property="og:image"]', 'content')
),
tocUrl: bookUrl
};
}日志规范
js
// 推荐:带模块标记的日志
legado.log('[search] keyword=' + keyword + ' page=' + page);
legado.log('[bookInfo] url=' + bookUrl);
legado.log('[toc] 共 ' + chapters.length + ' 章');
legado.log('[content] 长度=' + text.length);
// 推荐:关键数据记录
legado.log('[search] 找到 ' + results.length + ' 个结果');
legado.log('[explore] 分类: ' + categories.join(', '));ES5 注意事项
Boa 引擎仅支持 ES5 语法,以下现代语法不可用:
js
// ❌ 箭头函数
items.map(item => item.name);
// ✅ 普通函数
items.map(function(item) { return item.name; });
// ❌ const / let
const url = BASE + '/api';
// ✅ var
var url = BASE + '/api';
// ❌ 模板字符串
var msg = `Found ${count} items`;
// ✅ 字符串拼接
var msg = 'Found ' + count + ' items';
// ❌ 解构赋值
var { name, author } = book;
// ✅ 逐个赋值
var name = book.name;
var author = book.author;
// ❌ 展开运算符
var all = [...arr1, ...arr2];
// ✅ concat
var all = arr1.concat(arr2);
// ❌ for...of
for (var item of arr) {}
// ✅ for 循环
for (var i = 0; i < arr.length; i++) { var item = arr[i]; }性能建议
- 优先使用
legado.http.batchGet()批量请求,减少顺序等待 - 避免在循环中做不必要的 DOM 解析,解析一次后复用 doc 对象
processImage中及时调用legado.image.free()释放句柄- 搜索空页时尽早返回空数组,避免无效处理