项目使用了vue分片上传的插件vue-simple-upload(github地址https://github.com/simple-uploader/vue-uploader)。但是在此基础上,需要将样式、交互和功能与element-ui官方的el-upload组件靠拢,所以直接clone一份代码,准备直接修改(缝合)后build成单个js,直接作为内部库应用。通过观察require该js的结果{vue-uploader:{} }后, 我使用引用方式: Vue.use(require(“@/assets/js/vue-uploader.js”)[“vue-uploader”])。
问题描述
在修改了组件引用方式之后,在上传结束后出现了问题:<br /> ![image.png](https://cdn.nlark.com/yuque/0/2020/png/750718/1587109443274-8595cf34-d77a-463f-bdaa-0796d8eaad95.png#align=left&display=inline&height=67&margin=%5Bobject%20Object%5D&name=image.png&originHeight=67&originWidth=468&size=7497&status=done&style=none&width=468)<br /> 网上的标准答案是Object.assign()或者Object.keys()的参数传成null或者undefined时出现该问题,在本次代码里是在 delete this.data 处报错(this就是undefined),总结一下规律,猜想 delete是Object方法的变种,但是调用时参数不是支持的类型所以抛出该错误。<br /> 以上只是问题本身(what),接下来是why。为什么只是修改了vue和css部分,编译后会报错?<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/750718/1587111569372-0ec7d01e-0e41-4438-8194-56cd07128e96.png#align=left&display=inline&height=483&margin=%5Bobject%20Object%5D&name=image.png&originHeight=483&originWidth=297&size=26756&status=done&style=none&width=297)<br />其中1处send是准备发出请求的地方,下一步应该是用webAPIFileRead,根据分块的简单数据读取出要上传的chunk(包含文件流),然后再send然后test忽略,最后调用4处的_chunkEvent,其中根据status判断分块是否成功(?),决定是否上传。如果成功,下一步是调用this._updateUploadedChunks()开始上传并决定是否分片。<br />
问题出现的平台版本及自己尝试过哪些方法
排查到问题的契机就是我为了排除问题,删除了一下checkChunkUploadedByResponse(用于检查是否需要上传下一片),发现报错无了。也就是checkChunkUploaded为true时执行的独特内容是问题源,直接问题找到!
_updateUploadedChunks: function (message, chunk) {
//判断配置是否有变量checkChunkUploadedByResponse存在
var checkChunkUploaded = this.uploader.opts.checkChunkUploadedByResponse
if (checkChunkUploaded) {
var xhr = chunk.xhr
utils.each(this.chunks, function (_chunk) {
if (!_chunk.tested) {
var uploaded = checkChunkUploaded.call(this, _chunk, message)
if (_chunk === chunk && !uploaded) {
// fix the first chunk xhr status
// treated as success but checkChunkUploaded is false
// so the current chunk should be uploaded again
_chunk.xhr = null
}
if (uploaded) {
// first success and other chunks are uploaded
// then set xhr, so the uploaded chunks
// will be treated as success too
_chunk.xhr = xhr
}
_chunk.tested = true
}
}, this)
if (!this._firstResponse) {
this._firstResponse = true
this.uploader.upload(true)
} else {
this.uploader.uploadNextChunk()
}
} else {
this.uploader.uploadNextChunk()
}
},
其中,又有 `var uploaded = checkChunkUploaded.call(this, _chunk, message)`的`checkChunkUploaded`部分和压缩后浏览器定位到的代码很相似。<br /> 上面胡说乱说了一堆,我总结以下两点..<br /> 1, 根据uploader文件option内是否有checkChunkUploadedByResponse,决定了执行以下哪个方法: <br />this.uploader.upload(true) 会执行doneHandler,内含delete this.data。而 this.uploader.uploadNextChunk()不会。<br /> 2, 因为`this``.xhr.addEventListener(``'load'``, doneHandler, ``false``)`调用方式,决定了非严格模式下doneHandler内的this指向windows;严格模式下this是undefined,于是抛出这个错误。
相关代码
粘贴代码文本(请勿用截图)
//实例的send
send: function () {
var preprocess = this.uploader.opts.preprocess
var read = this.uploader.opts.readFileFn
if (utils.isFunction(preprocess)) {
switch (this.preprocessState) {
case 0:
this.preprocessState = 1
preprocess(this)
return
case 1:
return
}
}
//初始实例readState为0,如果加工后为2,说明成功可以跳过本次switch
console.warn("readstate",this.readState);
switch (this.readState) {
case 0:
this.readState = 1
//获取分片的文件,结束后再次调用send
read(this.file, this.file.fileType, this.startByte, this.endByte, this)
return
case 1:
return
}
//是否开启了testChunks用来验证服务端实现快传或者秒传,如果开启了强制测试一次
if (this.uploader.opts.testChunks && !this.tested) {
this.test()
return
}
this.loaded = 0
this.total = 0
this.pendingRetry = false
// Set up request and listen for event
this.xhr = new XMLHttpRequest()
this.xhr.upload.addEventListener('progress', progressHandler, false)
this.xhr.addEventListener('load', doneHandler, false)
this.xhr.addEventListener('error', doneHandler, false)
var uploadMethod = utils.evalOpts(this.uploader.opts.uploadMethod, this.file, this)
var data = this.prepareXhrRequest(uploadMethod, false, this.uploader.opts.method, this.bytes)
//原生的xhr发起请求,此前已经注册了监听
this.xhr.send(data)
var $ = this
function progressHandler (event) {
if (event.lengthComputable) {
$.loaded = event.loaded
$.total = event.total
}
$._event(STATUS.PROGRESS, event)
}
function doneHandler (event) {
console.warn("this.xhr",this, $);
var msg = $.message()
$.processingResponse = true
// console.warn("this invoke",$.xhr);
// processResponse: function (response, cb) {
// cb(null, response) //这里导致内部this指向window
// };
$.uploader.opts.processResponse(msg, function (err, res) {
$.processingResponse = false
if (!$.xhr) {
return
}
$.processedState = {
err: err,
res: res
}
var status = $.status()
if (status === STATUS.SUCCESS || status === STATUS.ERROR) {
delete this.data //=============================>问题就在这
$._event(status, res)
status === STATUS.ERROR && $.uploader.uploadNextChunk()
} else {
$._event(STATUS.RETRY, res)
$.pendingRetry = true
$.abort()
$.retries++
var retryInterval = $.uploader.opts.chunkRetryInterval
if (retryInterval !== null) {
setTimeout(function () {
$.send()
}, retryInterval)
} else {
$.send()
}
}
}, $.file, $)
}
},
总结
多看代码,保持敏锐,就算不能提高思考能力,也能及时定位问题。