项目使用了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”])。

问题描述

  1. 在修改了组件引用方式之后,在上传结束后出现了问题:<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时执行的独特内容是问题源,直接问题找到!

  1. _updateUploadedChunks: function (message, chunk) {
  2. //判断配置是否有变量checkChunkUploadedByResponse存在
  3. var checkChunkUploaded = this.uploader.opts.checkChunkUploadedByResponse
  4. if (checkChunkUploaded) {
  5. var xhr = chunk.xhr
  6. utils.each(this.chunks, function (_chunk) {
  7. if (!_chunk.tested) {
  8. var uploaded = checkChunkUploaded.call(this, _chunk, message)
  9. if (_chunk === chunk && !uploaded) {
  10. // fix the first chunk xhr status
  11. // treated as success but checkChunkUploaded is false
  12. // so the current chunk should be uploaded again
  13. _chunk.xhr = null
  14. }
  15. if (uploaded) {
  16. // first success and other chunks are uploaded
  17. // then set xhr, so the uploaded chunks
  18. // will be treated as success too
  19. _chunk.xhr = xhr
  20. }
  21. _chunk.tested = true
  22. }
  23. }, this)
  24. if (!this._firstResponse) {
  25. this._firstResponse = true
  26. this.uploader.upload(true)
  27. } else {
  28. this.uploader.uploadNextChunk()
  29. }
  30. } else {
  31. this.uploader.uploadNextChunk()
  32. }
  33. },
  1. 其中,又有 `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;严格模式下thisundefined,于是抛出这个错误。

相关代码

粘贴代码文本(请勿用截图)

  1. //实例的send
  2. send: function () {
  3. var preprocess = this.uploader.opts.preprocess
  4. var read = this.uploader.opts.readFileFn
  5. if (utils.isFunction(preprocess)) {
  6. switch (this.preprocessState) {
  7. case 0:
  8. this.preprocessState = 1
  9. preprocess(this)
  10. return
  11. case 1:
  12. return
  13. }
  14. }
  15. //初始实例readState为0,如果加工后为2,说明成功可以跳过本次switch
  16. console.warn("readstate",this.readState);
  17. switch (this.readState) {
  18. case 0:
  19. this.readState = 1
  20. //获取分片的文件,结束后再次调用send
  21. read(this.file, this.file.fileType, this.startByte, this.endByte, this)
  22. return
  23. case 1:
  24. return
  25. }
  26. //是否开启了testChunks用来验证服务端实现快传或者秒传,如果开启了强制测试一次
  27. if (this.uploader.opts.testChunks && !this.tested) {
  28. this.test()
  29. return
  30. }
  31. this.loaded = 0
  32. this.total = 0
  33. this.pendingRetry = false
  34. // Set up request and listen for event
  35. this.xhr = new XMLHttpRequest()
  36. this.xhr.upload.addEventListener('progress', progressHandler, false)
  37. this.xhr.addEventListener('load', doneHandler, false)
  38. this.xhr.addEventListener('error', doneHandler, false)
  39. var uploadMethod = utils.evalOpts(this.uploader.opts.uploadMethod, this.file, this)
  40. var data = this.prepareXhrRequest(uploadMethod, false, this.uploader.opts.method, this.bytes)
  41. //原生的xhr发起请求,此前已经注册了监听
  42. this.xhr.send(data)
  43. var $ = this
  44. function progressHandler (event) {
  45. if (event.lengthComputable) {
  46. $.loaded = event.loaded
  47. $.total = event.total
  48. }
  49. $._event(STATUS.PROGRESS, event)
  50. }
  51. function doneHandler (event) {
  52. console.warn("this.xhr",this, $);
  53. var msg = $.message()
  54. $.processingResponse = true
  55. // console.warn("this invoke",$.xhr);
  56. // processResponse: function (response, cb) {
  57. // cb(null, response) //这里导致内部this指向window
  58. // };
  59. $.uploader.opts.processResponse(msg, function (err, res) {
  60. $.processingResponse = false
  61. if (!$.xhr) {
  62. return
  63. }
  64. $.processedState = {
  65. err: err,
  66. res: res
  67. }
  68. var status = $.status()
  69. if (status === STATUS.SUCCESS || status === STATUS.ERROR) {
  70. delete this.data //=============================>问题就在这
  71. $._event(status, res)
  72. status === STATUS.ERROR && $.uploader.uploadNextChunk()
  73. } else {
  74. $._event(STATUS.RETRY, res)
  75. $.pendingRetry = true
  76. $.abort()
  77. $.retries++
  78. var retryInterval = $.uploader.opts.chunkRetryInterval
  79. if (retryInterval !== null) {
  80. setTimeout(function () {
  81. $.send()
  82. }, retryInterval)
  83. } else {
  84. $.send()
  85. }
  86. }
  87. }, $.file, $)
  88. }
  89. },

总结

多看代码,保持敏锐,就算不能提高思考能力,也能及时定位问题。