引言
最近公司的一个内部微信h5项目,在开发比较常见的一个功能(获取用户在活动公众号下的openId)时,出现了一个"诡异不详现象"(本人辰东的遮天粉,哈哈)。介绍这个诡异之前,先来简单介绍下这个功能的实现流程:
根据微信公众平台的开发文档中网页OAuth2.0鉴权规范,需要h5先重定向到指定页面,再重定向返回拿到授权码,再交由服务器端与微信公众平台通信拿取openId。
问题就出在重定向这里,这个页面的代码可以简易如下:
let authCode
function getAuthCode () {
if (当前页面url查询参数中有code这个key) {
authcode = getUrlSearchParams('code')
} else {
location.replace方法重定向
}
}
function getOpenId () {
将authcode传给服务器端,并将openId返回
}
getAuthCode()
getOpenId()
当时的开发同事预期的结果是:在执行getAuthCode方法时,如果执行到页面重定向,就会中止接下来的getOpenId方法入栈执行,直接重定向。但是实际的情况是在重定向跳转前,依然执行了getOpenId方法,于是向后端发起了接口请求,但是后端将code设置成必传参数,于是配合前端在axios响应拦截中的错误处理,将错误信息弹窗提示给了用户。
单这个事情的解决方法比较简单,在getOpenId方法内部执行前先判断一下authCode是否有值即可。但是现象是很让人有好奇心的,带着这份好奇心我们去探究一下具体原因
探索
第一步:demo搭建实验
我们先通过一些简单的demo配以效果视频看一下重定向详细的表现形式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function a () {
location.href = 'https://www.baidu.com'
}
a()
alert(1)
</script>
</body>
</html>
小结
我们在如果没有看到引言的情况下,可能预想到的是直接就跳转到其他页面了,后续的代码块并不会执行,然后真实的结果在Chrome浏览器中是先弹窗,然后再跳转
为啥是使用Chrome? 因为微信app使用的x5浏览器内核是非开源的,但是基于webkit做的调整,为了避免X5对重定向做了重大调整,我们可以拿上面的demo到Chrome和微信对比下现象,发现是一致的。这样我们才可以在第三步使用Webkit源码探究
第二步:百度查找
结果正如我们引言中所说的,我们探究的第一步就是去百度了,哈哈哈,看看有没有文章解答。搜了一些文章之后。大概分为两部分回答:
- 并不会出现执行后续代码
- 因为页面跳转是异步执行
两派人的回答肯定都不是无的放矢的,针对于第一种回答,我们在排除这些人没有手动实验的极端情况,那么他们为什么不会出现执行后续代码的情况呢,我们把关注点放到浏览器引擎差异上,猜测是不是用的引擎不同,就拿我们上述实验的Chrome举例,引擎是WebKit和以Webkit为基础的Blink。我们再换一种火狐浏览器的Gecko内核测试。
看完视频会发现,火狐下依然会执行弹窗alert,但是和Chrome的区别在于弹窗不会在手动点击前阻塞跳转。我又后续试了IE浏览器,但都执行了alert,所以针对第一部分的回答,在已测试的谷歌、火狐、IE浏览器下,都会执行跳转后的代码。如果其他小伙伴确实能测出来不会执行后续代码,烦请留言浏览器内核和版本号
小结
百度学习完之后,我们初步得到结论,重定向的表现形式上是异步,那么大家在思考一个问题,异步的话,那在我们执行了跳转代码多久后,才会真正跳转呢,是固定延时还是执行完后续所有的同步代码块,亦或者当前宏任务下的所有微任务。大家都可以去写demo去测试,但是为了真正搞清楚内部原理,我们还是要去研究一下WebKit引擎源码。
第三步:webkit源码分析:
大家可以从GitHub上下载Webkit的源码,友情提示,内容10G。请提前下载。我们不在此探讨目录结构等其他内容,感兴趣的同学可以去看相关源码解析,我们这一章只分析页面跳转的有关代码。另外牵扯到不同开发语言的语法差异,大家可以在阅读的同时,把C++语法的教程摆在旁边www.runoob.com/cplusplus/c…
- 使用的location.href属于window对象下的方法,那么我们要先找到window的创建文件 webkit\WebKit\Source\WebCore\page\DOMWindow.cpp。跳至760行,可以看到location实例的创建
- Location相关的模块文件在webkit\WebKit\Source\WebCore\page\Location.cpp,大家在这个文件下可以看到我们经常使用的location对象的常用属性,我们在通过为location.href赋值跳转,内部是使用的setHref方法。下图中所标注的代码行语法上概要理解成又回到了我们上面提到的DOMWindow,对象下的setLocation方法调用
-
回到DOMWindow.cpp文件第2378行,我们还是只看最后的一般流程执行,细心的朋友从scheduleLocationChange这个方法名上的schedule(规划)嗅到了一点感觉
-
navigationScheduler是在WebKit\Source\WebCore\loader\NavigationScheduler.cpp文件,在444行找到了scheduleLocationChange方法,老规矩,先不分析中间的特例,看到最后
-
在当前文件搜索schedule,可以查看到方法内部实现,中间判断有个可以多留意的地方,如果页面再加载期间安排了重定向,则停止当前加载。最底部的一个startTimer方法,正式的告诉了我们,探索到现在,你快发现了,居然有定时器!!!
-
如果是定时器的话,那到底会延时多久呢!我们看最关键的一段代码,延时的时间是由1s*当前要重定向的行为背景有关的。至于这个行为背景的详细分类我们试着在后面的章节枚举。
小结
一般的重定向delay都是靠近基数1s
第四步:对上一步的结论做验证
那么我们怎么验证呢,继续上demo,这个demo的核心就是在执行完跳转代码后,记录下当时的时间戳,然后不断循环,更新最新的时间戳。为了避免同步代码执行时间不够造成误差,我把循环的时间拉大到一分钟。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
const SECUND = 1000;
const MIN = SECUND * 60;
const HOUR = MIN * 60;
function a () {
location.href = 'https://www.baidu.com'
}
a()
let time = Date.now()
localStorage.start = time // 这种写法不要学习,此处为了省事。。。
let targetTime = time + MIN
for (; time < targetTime;) {
localStorage.end = Date.now()
}
</script>
</body>
</html>
最后我们来看结果,开始时间戳与最后的时间戳基本上差了1s
结语
我们的最终结论:webkit下重定向往往是延时一秒后的异步执行。那么以webkit为基础的浏览器内核,在没有调整上面我们分析的核心代码情况下,表现形式也是一样的。
那么当我们在使用重定向及页面跳转相关API的时候,要做好"善后"。即如果不希望重定向后的代码继续执行,建议增加一个哨兵变量,在后面代码执行前进行哨兵变量的状态判断。
文章最后的话,希望得到大家的建议和改进的地方,后面我们另开章节讲解不同的重定向背景导致所需的跳转延时