今天的你,写bug了吗?
唉什么都不想说,只怪自己太蠢。闭包和引用,两个最基础的知识,结合到了一起,却忘得一干二净,谨以此文,来记录下这个已经被我K.O的bug,愿来生路上,你不再出现。
情况是这样的,有个函数一个网络请求加处理步骤,结构大概是这样
let result = {}
function func(url) {
return new Promise((resolve, reject) => {
request(url, (err, res, body) => {
// 处理result并resolve出去
})
})
}
module.exports = func
本来觉得写一起好了,然后发现越写越长,于是就把它放到了另一个文件里面,然后主函数来引用。
不自觉为自己点个赞
于是乎,大刀阔斧地把这个函数给分离了出去,主函数的结构是这样
const func = require('./func')
let arr = []
let result = await func(url)
arr.push(unit)
最后来讲arr转换成字符串写入文件,结果这个时候就出现一个问题了,什么问题呢,写完发现文件里面的所有result都是最后一次返回的result,bug就此出现。
what happened?
其实聪明的你估计早就已经发现问题在哪儿了,但当是写了一天代码的我,头昏脑胀,完全不知道问题出在哪儿,定位了一圈,发现全出在这个引用的函数里面,然后我便去函数里面找bug,最后终于发现了问题,就出现代码开头的第一句里面,就是下面这句
let result = {}
这句定义了一个对象,然后经由func函数处理并返回,但问题就在这是个对象上面,下面一步一步来解释
result对象定义在func函数的外部,而模块exports出去的是这个函数,这样在主文件里面引用的时候,result共享的都是一个实例,这就是require的特性,也就是说,即使在主文件里面多次调用func函数,这样只要result函数在func函数内部没有改变指向,那么arr里面每次接收到的result都是一个实例,所以也就出现了虽然多次push都是不同的,但是在result改变时,arr里面的所有元素都会跟result同步改变。
其实一个很简单的例子就能体现出这个结果
let a = []
let b = {prop: 123}
for(let i=0;i<3;i++){
b.prop++
a.push(b)
}
这样最后你会发现a的内部所有的元素都会变为{prop: 126}.
回到上面的问题,主函数require进来的func其实相当于一个闭包,而这个闭包引用了result,而且由于require共享一个实例,这样每次改变result,自然arr的元素就会跟着改变了。
解决问题容易,但是如何避免问题,才是我们应该关注的问题。
这还只是个两个文件的程序,一个大型的应用程序可能几百个文件都很正常,这样如果某一个变量定义不慎,出现上面的问题,那么bug定位就比这个要难多了。
好了,今天的bug说就到这里,下期节目再见。
写bug去了