JS中on函数的回调方法

这个坑是我在开发寒假比赛项目的时候踩到的。大致的需求就是实现一个图片旋转方法,当图片的旋转角为逆时针90度时将图片传递给这个函数,在函数内部生成Canvas进行顺时针旋转90度的处理,最后将处理之后的结果返回出来。

复现问题

为了便于复现这个坑,我并没有重新写一个Demo,而是直接将代码展示出来。当时我的代码如下:

utils.js
const utils = {
  rotateImg: (imgSrc, vue) => {
    function getObjectURL (object) {
      return (window.URL) ? window.URL.createObjectURL(object) : window.webkitURL.createObjectURL(object)
    }
    function dataURLtoBlob (dataurl) {
      var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
      }
      return new Blob([u8arr], { type: mime });
    }
    var canvas = document.createElement('canvas')
    var context = canvas.getContext('2d')
    var image = new Image()
    var testSrc
    image.src = imgSrc
    image.onload = () => {
      var imgRealWidth = image.width
      var imgRealHeight = image.height
      canvas.width = imgRealHeight
      canvas.height = imgRealWidth
      context.rotate(90 * Math.PI / 180)
      context.drawImage(image, 0, -imgRealHeight)
      var result = canvas.toDataURL('image/png')
      var blobObj = dataURLtoBlob(result)
      var file = new File([blobObj], `${Date.now()}.png`, {type: 'image/png'})
      var pushSrc = getObjectURL(file)
      testSrc = getObjectURL(file)
      vue.imgList.push(pushSrc)
    }
  },
  ...
}

export default utils

因为这段代码是放在外部JS文件中的,所以采用了ES6模块的语法编写。

在Vue组件内部使用EXIF判断,如果符合条件就传给Utils中的rotateIMG函数处理。问题的关键就在于转换成Image对象时只有使用.onload方法才能继续Canvas处理,而由于闭包的原因,处理之后的URL已经很难传给Vue了。

当时我使用的时一个土办法:将Vue对象传入函数,处理完之后直接往Vue中的目标数组push进去。这个办法虽然最后成功实现,但是心里还是不踏实。毕竟是一个本办法,解决JavsScript中事件函数传值的问题一定还有其他更先进或是我以前没有掌握的方法。

找到答案

放心不下,于是我特地去StackOverflow上寻找答案,终于在该网站的image.onload function with return上找到了答案。

在赞数最多的答案中,答主给了一个比较可行的方案:

The value is returned from the handleLoad function to the code that calls the event handler, but the detect function has already exited before that. There isn't even any return statement in the detect function at all, so you can't expect the result to be anything but undefined.
One common way of handling asynchronous scenarios like this, is to use a callback function:

代码:

function detect(URL, callback) {
  var image = new Image();
  image.src = URL;
  image.onload = function() {
    var result = [{ x: 45, y: 56 }];
    callback(result);
  };
}
You call the detect function with a callback, which will be called once the value is available:

代码:

detect('image.png', function(result){
  alert(result);
});

答主采用了据称比较流行的做法:在函数参数的最后添加一个回调函数,在定义函数的函数体中只需在最后调用这个回调函数,并将想要传出的参数写在参数列表中即可。调用这个函数时,只需要将参数逐一对照就能得到相应的回调参数值。

解决问题

基于这个思路,我很快就写出了加入回调方法的代码:

utils.js
const utils = {
  rotateImg: (imgSrc, callback) => {
    function getObjectURL (object) {
      return (window.URL) ? window.URL.createObjectURL(object) : window.webkitURL.createObjectURL(object)
    }
    function dataURLtoBlob (dataurl) {
      var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
      }
      return new Blob([u8arr], { type: mime });
    }
    var canvas = document.createElement('canvas')
    var context = canvas.getContext('2d')
    var image = new Image()
    image.src = imgSrc
    image.onload = () => {
      var imgRealWidth = image.width
      var imgRealHeight = image.height
      canvas.width = imgRealHeight
      canvas.height = imgRealWidth
      context.rotate(90 * Math.PI / 180)
      context.drawImage(image, 0, -imgRealHeight)
      var result = canvas.toDataURL('image/png')
      var blobObj = dataURLtoBlob(result)
      var file = new File([blobObj], `${Date.now()}.png`, {type: 'image/png'})
      var pushSrc = getObjectURL(file)
      callback(pushSrc)
    }
  }
}

export default utils
Vue组件中
EXIF.getData(imgFile, () => {
  var tempData = EXIF.getAllTags(imgFile)
  if (tempData.Orientation == 6) {
    console.log(true)
    Utils.rotateImg(imgURL, result => {    //调用回调函数,用result拿到URL
      _this.imgBlobURL = result
    })
  } else {
    _this.imgBlobURL = imgURL
  }
})

总结

通过这次踩坑,我终于接触到了本应很久之前就该学会的回调函数的内涵与编写方法。

将最新的文章发送到你的邮箱

展示评论