背景
在之前的工作当中,遇到有文件上传的需求基本都是通过引用插件来实现的,效果是完成了,但是其实并没有一个系统的认识,理解比较粗浅。
鲁迅曾经曰过:
好读书,也要求甚解。诸葛村夫不求甚解,所以多智也只能近妖。
最近又遇到了相关的需求,在阅读了Deng mu qin(大神都是这样的,只留下了一串拼音字符,不带走一片云彩~)前辈的upload.js
源码后,觉得可能跟业务比较耦合,通用性相对不是那么好,所以决定自己撸一个文件上传的小插件,既当是学习,同时也吸(chao)取(xi)一下前辈的人生经验。第一次写技术文章,其实技术性谈不上多强,主要是提醒自己要不断学习、不断总结,希望以后能成为一方小牛。真心希望能多多讨论,一起进步!
一些热身准备
FileUpload对象
初来乍到,萌新们可能跟我一样对FileUpload
对象一无所知,无妨,先看一个最简单的例子:
1 | <input type="file"> |
当上面的标签出现在页面中时,一个FileUpload
对象就会被创建,然后就会出现一个大家熟悉的银灰色小方块,点击选择文件,出现对应的文件名称和格式。
XMLHttpRequest请求
现代浏览器中(IE10 & IE10+),XMLHttpRequest
请求可以传输FormData
对象,可以通过XMLHttpRequest
对象的upload
属性的onprogress
事件回调方法获取传输进度,这也是在下的xupload.js
的安生立命之本。至于IE9IE8IE7IE6,emmmm…
告辞。
注册插件
通过一个经典的自执行匿名函数,再将方法注册到jQuery
上,就可以基本实现一个jq插件的初步建立:
1 | // ;冒号防止上下文中有其他方法未写;从而引起不必要的麻烦 |
代码解析
Upload构造函数
一个构造函数需要做些什么呢?
- 通过挂载到
this
的方式,初始化一些后续需要使用到的变量,此过程可以视后续代码需要不断增量更新 - 配置一个
defaultConfig
默认配置项,在用户直接调用xupload
方法时直接使用配置项,当然,当用户传递属于自己的配置项时,需要将用户配置项跟默认配置项进行更新合并,此时可以用到jQuery
的extend函数 - 调用初始化函数
代码如下:
1 | function Upload(config) { |
构造函数原型的结构
prototype
在我看来有点类似于class
之于css
,你能想象如果css
中没有class
会发生什么吗?可用性和复用性都成了灾难,这是绝对不行的。
关于prototype
的进一步解读,大家可以参考一下方应杭老师的精彩解读。
想象一下,我们把一些常用的工具方法挂载到prototype
上,这样调用一个实例,这个实例就自动继承了所有在prototype
上的方法,修改一下prototype
,所有实例也都自动响应过来,是不是跟css
中的class
很像呢?
那么让我们来设计一下Upload
的原型函数需要哪些基础的方法吧:
- 首先需要一个
init
初始化函数,在这里调用必须用到的方法。
仔细想想,一个上传插件,第一步最需要的是不是响应用户选择文件的操作呢?再进一步,页面中是否只有一个上传input
?
原方法
1 | init: function () { |
更新于20180423
1 | init: function () { |
- 其次需要一个处理函数
handler
,去负责接下来具体的逻辑,比如规则的验证、图片预览等等
原方法
1 | handler: function (e, files) { |
更新于20180423
1 | handler: function (e, files) { |
原方法
然后需要一个触发器函数triggerUpload
,能够自动或者手动的执行接下来的上传操作
1 | triggerUpload: function () { |
更新于20180423
- 然后需要一个触发器函数
triggerUpload
,能够自动或者手动的执行接下来的上传操作,然后再多思考一步,用户会不会只想上传其中某一个文件呢?这是完全有可能的,所以我们得提供多一种思路,这里我们可以使用“函数重载”,当用户不传值时,则默认全部上传,如果传入了指定的index
值,则单独上传该文件,之所以带引号,是因为确实只是通过简单的参数去实现的,更高级的函数重载,可以参考jQuery之父John Resig利用闭包巧妙实现的重载 译文。
1 | triggerUpload: function (index) { |
- 接下来就是重头戏
upload
了,需要这样一个函数去处理上传的POST
请求,同时暴露出一些状态函数,比如onloadstart
、onerror
等等
1 | upload: function (files) { |
新增于于20180423
- 接着让我们自己封装一个预览方法
previewBefore
吧。首先应该明确的是需要一个预览容器,不然图片不知道改放哪;接着图片的样式我们也应该让用户去控制(暂时没有做模版),所以有两个传入的新属性previewWrap
、previewImgClass
,顾名思义。
1 | previewBefore: function () { |
新增于于20180423
- 有了预览,是不是还差个删除呢,让我们回想
triggerUpload
方法,此时应该也沿用那种思想,传入指定的index
值去删除指定的文件,不传值则默认删除所有。
1 | delBefore: function (index) { |
- 同时需要一些私有状态函数来接收
xhr
的事件回调方法,然后”call”一下暴露在外的config
里面的对应的函数,疯狂打call后,就可以在外边接收到xhr
的事件回调啦
1 | // 开始上传 |
- 当然验证方法
validate
是必不可少的,但是这里我只是通过rules
简单的定义了一些规则,而且感觉这块其实应该给用户去自定义,然后我在代码里面去转义成我的代码能看懂的方法,这里还需要改进,也欢迎大家提宝贵意见
1 | validate: function (files) { |
- 具体的规则呢就需要交给具体的人去处理,男女搭配干活不累,说的就是你,
rules
大妹子
1 | rules: function (item, index) { |
- 同时可能需要一些工具方法,比如在还未上传的时候去
get
和set files
的值呀,暂时想到的是这些
1 | get: function () { |
插件使用
1 | var up = $.xupload({ |
总结
第一次写类似的插件,运用的技巧比较简单粗浅,也有很多不足,已经在计划改进了,大牛轻喷,以后会更加努力的(ง •̀_•́)ง。
虽然看到这篇文章的人可能不多,但是刘备也曾经曰过:
勿以善小而不为
我这叫做“善”好像也有点牵强…总之就是那么个意思!
emmm…好像也没啥说的了,大家都是面向工资编程,那就祝大家早日一夜暴富吧。
代码是什么,能吃吗?
Todo
- 文件的拖拽上传
- 文件的取消上传,重新上传
- 一些其他细节和bug处理