## 分析
意外发现博客的某篇文章在手机端打开的时候一直处于加载中的状态,但是点开其他文章却又是正常的,推断可能是前端页面在渲染的过程中遇到某些错误被中断了。
> chrome 浏览器直接打开 `mobile` 模式还是能正常显示,无法完全模拟手机端的情况
## 复现
由于手机端的浏览器无法直接进行调试,需要通过 usb 连接到电脑,再使用 chrome 浏览器结合 pc 和手机端进行调试。
手机端调试接入的方式参考:
https://blog.csdn.net/weixin_32149443/article/details/112936241
> 这篇博文是转载的,且格式杂乱无章。。csdn的水文真是越来越多了,原文是 cnblog 的一篇文章,但是已经无法显示了,将就着看吧。
接入调试后发现,是文章内容在渲染的时候 jquery.js 抛出了错误,导致内容的渲染被中断了。

点进错误信息里面我们可以看到异常发生的具体是哪行代码,如下图

从上图的第二处可以看出,抛出异常的是 `formatMath` 这个方法,从注释和代码上来看,这个方法的具体作用可能是对文章内容中出现的一些公式做处理。
> 前期推测可能是传入的某一次循环中传入的 `kateBlock` 值为 null 对象,导致无法调用 `replaceAll` 方法(该方法只有在字符串类型时才存在),也就是 Java 中经常遇到的空指针异常(没有在使用某个对象前对其值为空的情况做出处理)只是在 js 体现的不一样。
在渲染别的文章内容时基本没有触发引用 `dealMathx` 这个方法,所以这个问题也没有被发现。
## 解决
从 [stackoverflow](https://stackoverflow.com/questions/62825358/javascript-replaceall-is-not-a-function-type-error) 这篇文章中得知,有可能是浏览器内核对 `replaceAll` 这个方法的兼容性问题导致的。
本篇文章中的安卓 chrome 版本是 `78.x`,而不在官方给到的支持版本范围内,如下图


到这里我们知道 `replaceAll` 这个 `js api` 可能会出现浏览器兼容性问题,是否有其他代替方案?
**使用 `RegExp` 结合 `replace` 实现 `replaceAll` 的效果,实现更好的兼容性。**
编写一个 `escapeRegExp` 方法,将部分字符进行转义,返回一个合法的正则表达式字符串
```js
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
```
自定义 `replaceAll`
```js
function replaceAll(str, match, replacement){
return str.replace(new RegExp(escapeRegExp(match), 'g'), ()=>replacement);
}
```
修改 `Xue` 主题 `main.js` 中的 `formatMath` 方法,将原有的 `replaceAll` 替换为我们自定义的 replaceAll
```js
/**
* 格式化公式
*/
function formatMath(kateBlock, isBlock) {
// 这一步很重要,需要把 & 换成 &
//var block = kateBlock.replaceAll("&", "&");
var block = replaceAll(kateBlock,"&", "&")
if (block.length < 3) {
return;
}
// ... ...
}
```
修改即可正常渲染文章的内容(可以使用 halo 自带的主题编辑功能进行测试)

## 正则表达式 “误判” 的问题
`Halo` 博客的 `Xue` 主题中的 `main.js` 内用于识别 **行内公式** 正则表达式错误的匹配到了我文章的内容,并进行了替换,导致文章内容显示异常。
main.js 内的源码:

正则匹配测试

渲染错误的效果

我们来复盘一下这条正则表达式:`/\$([\s\S]*?)\$/g`
其意义是在字符串全局范围内,匹配以 `$` 号开头以及结尾,中间的内容为多个空格或者非空字符(几乎包含了任意字符)
平时很少用 `markdown` 去写一些公式,所以去查了一下语法,其特征确实是以两个 `$` 号来标识为一个公式

正好这篇文章的 `markdown` 中有两段内容包含了 `${}` 用作 `freemarker` 的变量,导致内容在渲染出来后被前端替换

### 解决方案:
- 将匹配行内公式的正则表达式中的 `[\s\S]*?` 替换为 `.*?` 实现匹配任何非换行符的字符。
> 正因为是行内公式,所以也不需要匹配换行符,合情合理。。
- 且需要考虑文章内也出现包含 `\$xxx\$` 的正则表达式的情况
所以最终将 `/\$([\s\S]*?)\$/g` 替换为 `/\$(?!\\$)(.*?)\$(?!\\$)$/g`
- 这里用到了正则表达式中的 `负向先行断言` 的概念,例如 `exp1(?!exp2)` 中正则 exp1 匹配的内容需要在正则 `exp2` 中不成立,想进一步的学习该内容可以参考 [这篇文章](https://blog.csdn.net/q965844841qq/article/details/111368696),这里不就不过多的赘述。
- 这个问题也间接的让本篇文章所提到的 `replaceAll` 这个 api 兼容问题浮出了水面 🤣
## 总结
- 在使用第三方库(例如 JQuery.js)时,尽可能使用更兼容型的 api 去实现需求。
- 在使用正则表达式对某些内容最匹配时,尽可能更多的特征条件,避免错误的匹配。
- 浏览器会缓存js文件,在修改后,最好先清空缓存,或打开控制台 `network` 勾选 `Disable cache` 在进行测试。
## 参考资料
- [Javascript .replaceAll() is not a function type error](https://stackoverflow.com/questions/62825358/javascript-replaceall-is-not-a-function-type-error)
- [String.prototype.replaceAll()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replaceAll#browser_compatibility)
- [Regular expressions
](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions)
- [正则表达式 不以 字符串 开头结尾 (前后预查) look around](https://blog.csdn.net/q965844841qq/article/details/111368696)
从 “string.replaceAll is not a function” 中复盘 JQuery.js 兼容性问题