多文件上传获取图片原始尺寸时的闭包问题与作用域异常处理
多文件上传批量校验图片宽高的作用域异常修复
问题成因
原循环代码使用var声明所有内部变量,由于var是函数级/全局作用域而非块级作用域,循环会在同步执行阶段瞬间跑完,将所有FileReader、Image的异步回调全部注册完成,直到所有同步代码执行完毕,这些异步回调才会依次触发。此时所有回调引用的i、iteratedFile、reader、image都是共享的同一个全局绑定,最终会出现这些异常:
- 循环执行完毕后
i的值等于上传文件的总数量,已经超出文件数组的有效下标范围,因此在回调中读取fileUpload.files[i]会返回undefined iteratedFile在每轮循环被重新赋值,循环跑完后保留的是最后一次迭代的文件对象,因此所有回调里读取到的都是最后一个文件的属性- 之前尝试手动传参触发的404错误,本质是传参时没有做作用域隔离,异步回调触发时拿到的索引值已经异常,最终
image.src被赋值为异常值,导致浏览器向当前页面路径请求名为undefined的资源,抛出404。
修复实现
方案1:块级作用域声明(推荐,现代浏览器全兼容)
将循环内需要被异步回调引用的变量从var改为let,每轮循环都会生成独立的块级变量绑定,每个异步回调都会引用当次循环对应的独立变量,从根源上解决共享作用域的问题。修正后的代码:
for (let i = 0; i < fileUpload.files.length; i++ ) { // 每轮循环生成独立的reader、image实例,通过let绑定当前循环作用域 let reader = new FileReader(); let image = new Image(0, 0); let iteratedFile = fileUpload.files[i]; // 读取当前轮次的文件 reader.readAsDataURL(iteratedFile); reader.onload = function () { image.src = this.result; image.onload = function () { const height = this.naturalHeight; const width = this.naturalWidth; if (height > 100 || width > 100) { // 可正常获取当前轮次的文件属性 console.log(iteratedFile.name); console.log(iteratedFile.type); console.log(fileUpload.files[i]); // 后续图片缩放处理逻辑 } }; }; }
方案2:立即执行函数隔离(兼容老旧无块级作用域的环境)
如果需要在不支持let的低版本浏览器环境运行,可以通过立即执行函数,在每轮循环时把当前的索引、文件对象传入独立的函数作用域,让异步回调引用独立的闭包变量:
for (var i = 0; i < fileUpload.files.length; i++ ) { // 立即执行,将当次循环的索引、文件封装在独立作用域内 (function(currentIndex, currentFile) { var reader = new FileReader(); var image = new Image(0, 0); reader.readAsDataURL(currentFile); reader.onload = function () { image.src = this.result; image.onload = function () { var height = this.naturalHeight; var width = this.naturalWidth; if (height > 100 || width > 100) { // 正常获取当前轮次的文件属性 console.log(currentFile.name); console.log(currentFile.type); console.log(fileUpload.files[currentIndex]); } }; }; })(i, fileUpload.files[i]); }
注意:每轮循环独立创建
Image实例的写法,也可以避免多文件加载时src被跨轮次覆盖的问题。
内容的提问来源于stack exchange,提问作者DevelJoe
相关产品推荐
相关产品推荐

