今天被同学问了一个 bug,属于平时很难察觉的方面,特地写篇文章介绍一下。

起因

今天被同学求助,帮忙看看代码有什么问题,想要实现的功能很简单,就是一个登录窗口的弹出与关闭。但遇到了 bug,窗口无法关闭。

代码展示如下,省略了部分内容,你能发现其中的问题吗?

<div class="top">点击,弹出登录框</div>
<div class="box">
  <span class="close">关闭</span>
  <div class="title">登录</div>
</div>
var top = document.querySelector('.top')
var box = document.querySelector('.box')
var body = document.body
var close = document.querySelector('.close')
top.addEventListener('click', function () {
  box.style.display = 'block'
  body.style.background = '#b2b2b2'
})
close.addEventListener('click', function () {
  box.style.display = 'none'
  body.style.background = ' '
})

起初我看代码感觉逻辑挺正确的,看他的测试结果事件也正确绑定了

折腾了一会,直到让他把代码发给我,亲自调试,才发现其中的猫腻

打印 top 变量,指向的竟然是 window ???

每次点击关闭,都因为事件冒泡,又把关闭的窗口打开了

解决办法很简单,变量改个名就行了

但这种情况我也是第一次遇见,十分诡异,就研究了其原因

原因

说到底,这是因为 var 所导致的 bug

我们知道,var 声明的变量都是绑定到 window 身上,我们使用这些变量省略 window. 的部分,然而 window 身上自带了一些变量,它们是不可改写的。

而 var 声明已有的变量,也不会报错。这就导致我们以为声明了一个变量并正确赋了值,实际上使用的却是浏览器内置的 window 属性。

window.top 属性,返回的是窗口层级最顶层窗口的引用,是个不可改写的属性。也就导致在之前代码中声明 top 节点绑定事件,事件实际却绑定到了 window 身上。

window 身上的属性很多,高达 200 个,其中不乏我们平时常用的变量名,name length parent self 等。只是比较幸运的是,这些变量都是可写的,我们声明它们并使用,不会引发什么错误。

为什么又说是因为 var 导致的呢?因为如果你使用 let 定义 top 变量,会直接报错

let top = 1 // SyntaxError: Identifier 'top' has already been declared

对于那些可写的 window 属性,let 可以正常声明并赋值,不会修改 window 上的属性,使用时也是用 let 声明的变量。

解决

这种问题。不经常遇到,但真遇到了,很难排查出来。

window 属性那么多,以后还会不断新增,搞不好什么时候就遇到了。

在此提出一个解决办法:一是要拒绝使用 var 只用 let const 来声明变量,二是开启 JavaScript 文件的检查功能。

在 vscode 中,可以在 setting.json 文件中设置 "js/ts.implicitProjectConfig.checkJs": true 来启用 JavaScript 文件的语义检查。

QQ图片20220603214623.png

这个语义检查功能很好用,能帮你排查 js 文件中不合规范的地方,比如参数类型,变量是否已声明等等,有助于我们养成良好的代码习惯。

QQ图片20220603214911.png

结语

如果文中有错误或不严谨的地方,请务必给予指正,十分感谢。

如果喜欢或者有所启发,希望能点赞关注,鼓励一下作者。


清隆
29 声望2 粉丝

学完某项技能一定要写写文章,用的时候都是照搬代码,写出来才能深入理解!