项目中希望我能上传图片,并对图片进行处理,并在预览框显示结果。
对此我采用vue框架中element组件的upload来进行实现:
官方示例demo:Upload 上传
在使用的过程我需要将上传的JPG格式FILE文件转换为base64字符串用于其余业务逻辑处理,而在这过程中我遇到了麻烦(由于自身对于js不熟练),困扰了我数个小时。
这个故障我描述为FileReader.onload
绑定的回调函数内无法用this
来引用全局变量,对该变量的赋值对外无效
由于印象深刻,我特意记录下来,本篇将分为两部分,一部分是我整套流程中可以通用的代码,另一部分是我遇到这个故障的思考路径和最终解决方案。
提前总结:函数内部和外部所用的this指代内容不相同,因此需要用一个其他变量名传递进去。
这段代码的实际功能等效于,上传并预览图片,但在预上传函数中,可以获取到图片的字符串格式,在某些情况下或许有用。
控件部分:
<el-upload
class="avatar-uploader"
action=""
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload">
<img v-if="imgsrc" :src="'data:image/jpeg;base64,' + imgsrc" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
变量部分:
return {
imgsrc:'',
}
属性部分:
<style scoped>
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #409EFF;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 240px;
height: 240px;
line-height: 240px;
text-align: center;
}
.avatar {
width: 240px;
height: 240px;
display: block;
}
</style>
函数部分:
handleAvatarSuccess(res, file) {
//this.imageUrl = URL.createObjectURL(file.raw);
},
beforeAvatarUpload(file) {
const isJPG = file.type === 'image/jpeg';
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isJPG) {
this.$message.error('上传头像图片只能是 JPG 格式!');
}
if (!isLt10M) {
this.$message.error('上传头像图片大小不能超过 10MB!');
}
if(!(isJPG && isLt10M))
return isJPG && isLt10M;
//使用前应该判断一下FileReader是否可用,但我忘了
let reader = new FileReader();
let _this = this;
reader.onload = function(e) { //先绑定回调
let base64Str = e.target.result;
_this.imgsrc = base64Str;
};
reader.readAsDataURL (file); //再准备读取
return isJPG && isLt10M;
},
故障代码原始为:
reader.onload = function(e) { //先绑定回调
let base64Str = e.target.result;
this.imgsrc = base64Str;
};
对比上方代码可以看出我改变的地方,我遇到的故障就是对于this.imgsrc
的赋值无效,这使得我对于上传的图片无法预览。
以下是我的探索记录:
我先修改了变量名,一方面减少其他因素对此的影响,另一方面方便复原代码环境,再加入打印:
beforeAvatarUpload(file) {
const isJPG = file.type === 'image/jpeg';
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isJPG) {
this.$message.error('上传头像图片只能是 JPG 格式!');
}
if (!isLt2M) {
this.$message.error('上传头像图片大小不能超过 2MB!');
}
//this.imgsrc2 = "123";
let reader = new FileReader();
console.log("flag2.4-----"+this.imgsrc2);
//this.imgsrc2 = "147";
reader.onload = function(e) { //先绑定回调
let base64Str = e.target.result;
console.log("flag2.2-----"+this.imgsrc2);
this.imgsrc2 = "456";
console.log("flag2.2-----"+this.imgsrc2);
};
reader.readAsDataURL (file); //再准备读取
return isJPG && isLt2M;
},
我是这么声明这个变量的:
export default {
data() {
return {
imgsrc2 : ''
}
}
}
打印信息:
flag2.4-----
flag2.2-----undefined
flag2.2-----456
flag2.4-----
flag2.2-----undefined
flag2.2-----456
这一段的出现就表现了问题:flag2.2-----undefined
如果上述函数执行两次,是不应该出现这个问题的(可以确保没有其他地方有对个变量的赋值)
这方面首先我回顾我对vue框架的全局变量的理解,应该是在return 属性中的data部分声明的属性。
我怀疑我对全局变量的理解有错误,但我认为可能不是关键。
我认为声明的方式没有错,使用时用this.imgsrc2进行调用,.vue模板文件中,this关键字可以访问到Vue.prototype上挂载的所有对象,所以写this就相当于写Vue.prototype。
正常情况下是可以赋值的的,但是当进入了函数function(e) 后,就无法进行赋值了。
对此我做出猜测,这个异步的函数很可能跟我正常顺次运行的函数不在同一个时空,因此获取变量对象时,实际获取的是一个镜像变量,虽然同名但是不能对外传递数值。
我使用了watch,但是可以观测到,函数内部进行修改值外部是察觉不到的。
watch: {
imgsrc2: {
handler(newOption, oldOption) {
if(newOption)
console.log("flag2.5-----"+newOption);
else
console.log("flag2.6-----"+oldOption);
},
immediate: true,
deep: true,
},
}
这上方函数中的打印没有被执行。
我已经尝试了对变量名进行修改,因此下一步就是修改变量的另一个构成因素this
let _this = this;
reader.onload = function(e) { //先绑定回调
let base64Str = e.target.result;
console.log("flag2.2-----"+_this.imgsrc2);
_this.imgsrc2 = "456";
console.log("flag2.2-----"+_this.imgsrc2);
};
(从打印来看赋值成功)
这个方案是我基于猜想时空不同进行的,因为函数内部属于另一个函数,所以导致用this进行定位时和外部的不重合,因此异步时间不同,定位空间不同导致值传递失败,但是现在用一个中间变量将外部环境的this基于中间变量传入内部,使得获取锚点因此可以进行赋值。故障解除。
这个故障用了我大概4小时时间,因为我不太能理解为什么这个变量明明看起来描述的是同一个,但是赋值时却不是同一个,最终我的猜想是内部的函数引用的是外部变量的镜像。如果有高手看到这一篇对于js比较熟悉的,有空请解惑,谢谢。
(积少成多,积少成多)