一个eval引发的血案

昨天碰到一个线上问题,一个页面在一般的情况下加载执行,它的交互功能是正常的,但使用异步方式加载它的内容进来塞页面里,交互功能就丢掉了。

同步加载的情况就不说了,先介绍下异步的加载情况。

以异步方式请求同一个页面,带上特殊参数的时候,服务端会依据一些页面中埋的注释当做定界符,把页面拆成小块小块的,比如头部一块,胸部一块(←_←听起来怪怪的),腿部页脚一块。变成“半结构化数据”,其中每一块都是HTML片段,我们出问题的那一段就管叫body吧。

由于body里夹杂了一些JavaScript,又由于一些复杂的历史原因1,在异步拿到这些半结构化数据之后,需要将body里的<script>提取出来妥善处理之后依次执行。

在大多数情况下这个程序虽然看起来很trick,但是好赖运行得不错。可昨天突然遇到一个BUG,就是某个页面里的body在异步加载的方式下,交互功能就没了。首先可以用脚趾头确定这肯定是JS执行的问题,然后看了下这个页面的body内容,发现它里面有三段<script>,单步执行进去看,在异步的情况下都匹配出来了,也都执行了,但交互没生效。而将其中某一段摘出来扔控制台里再执行一遍,交互就回来了。

仔细观察这段问题代码,它定义了一个全局函数2,定义就定义吧,这么多页面也没能一个个code review,且行且珍惜。


以上就是故事背景了(比尼玛正文还长)。那为什么全局函数在这里就失效了呢?究其原因是因为eval

在上面提到的那个加载器里面,会将匹配出来的<script>eval方式执行的,eval是局部作用域、局部作用域、作用域、域——为了凸显本文的核心问题,我将这句话的混响调长了一点点。而且这还不是问题的全部,更不可思议的是这也有浏览器兼容性问题3,具体内容太复杂了,我就不搬运了,大家搜一下就行。

书接上回,在那个交互里,点击的时候会调用它之前定义的那个全局函数,如果正确执行的话那就刚刚好。而现在eval了之后,那个全局函数自然没被定义到全局上面,就挂了。神奇的是这段代码很良心的加了try/catch,于是也没看到控制台里有报错。

事已至此,只要倒霉加载器改用“全局eval”就能解决这个问题了(喂喂不是应该让做那个页面的人重构代码吗?),具体的方式我也不搬了,大家自己搜吧。


附注

  1. f*cking historical reasons
  2. f*cking global function
  3. f*cking browser compatibility problems

参考文献

  1. 让eval()全局作用域执行的方法深入研究(javascript)
  2. globalEval函数