本篇文章給大家?guī)砹岁P(guān)于前端上傳文件的相關(guān)知識,其中包括傳統(tǒng)開發(fā)模式上傳、前后端分離上傳以及ajax上傳等等相關(guān)問題,希望對大家有幫助。
上傳文件
項目中會有很多文件上傳的需求,例如:頭像上傳、表格文件、word文檔等…
上傳必備表單元素:
<input type="file">
進(jìn)行文件上傳的時候,
1.表單必須是post請求
2.表單必須聲明不要對數(shù)據(jù)進(jìn)行編碼 – enctype=multipart/form-data
傳送數(shù)據(jù)的格式就是鍵值對的形式,且數(shù)據(jù)都是js的數(shù)據(jù)類型,但文件進(jìn)行傳輸?shù)臅r候,只有兩種形式去傳輸:
- 以字符串的形式去描述一個文件
- 以文件流的形式去描述一個文件
傳統(tǒng)開發(fā)模式上傳
前后端混在一起開發(fā)
傳統(tǒng)開發(fā)模式的上傳需要將表單中選擇的文件傳送給后端,讓后端做上傳:
<form action="./upload.php" method="post" enctype="multipart/form-data"> <input type="file" name="avatar"> <input type="submit" value="上傳"></form>
此時的表單中必須有enctype這個屬性,這個屬性的說明如下圖:
點(diǎn)擊上傳按鈕后,后端對文件進(jìn)行上傳處理,以php為例:
echo "上傳文件名: " . $_FILES["avatar"]["name"] . "<br>"; 上傳文件的名稱 echo "文件類型: " . $_FILES["avatar"]["type"] . "<br>"; 上傳文件的類型 echo "文件大小: " . ($_FILES["avatar"]["size"] / 1024) . " kB<br>"; 上傳文件的大小,以字節(jié)計 echo "文件臨時存儲的位置: " . $_FILES["avatar"]["tmp_name"]; 存儲在服務(wù)器的文件的臨時副本的名稱 echo $_FILES["file"]["error"] 由文件上傳導(dǎo)致的錯誤代碼
將文件保存到服務(wù)器中:
move_uploaded_file($_FILES["avatar"]["tmp_name"], "upload/" . $_FILES["avatar"]["name"]); echo "文件存儲在: " . "upload/" . $_FILES["avatar"]["name"];
在實(shí)際開發(fā)中,為了提高效率,通常都會使用前后端分離開發(fā)。
前后端分離上傳
前端做前端,后端做后端,最終使用接口文檔對接 – 核心技術(shù)是 ajax
前后端分離開發(fā),應(yīng)用的主要技術(shù)就是ajax。上傳同樣可以使用ajax來上傳。
FormData是js內(nèi)置的一個構(gòu)造函數(shù),構(gòu)造出來的對象可以識別文件信息。
使用方式:
構(gòu)造FormData對象,將文件信息添加到FormData對象中,然后上傳。
文件信息在文件選擇控件中:表單.files
例:
<body> <input type="file" name="avatar"> <input type="button" value="上傳"></body><script>document.querySelector('[type="button"]').onclick = function(){ console.log(document.querySelector('[type="file"]').files)}</script>
FormData對象有一個特點(diǎn),將文件信息添加進(jìn)去后,直接打印不能看到文件信息,需要使用for of遍歷才能看到:
var formdata = new FormData();var fileinfo = document.querySelector('[type="file"]').files[0];formdata.append('avatar',fileinfo) / 將文件信息添加到FormData對象中 console.log(formdata)for(var v of formdata){ console.log(v)}
FormData對象中也可以添加別的數(shù)據(jù),進(jìn)行一起提交:
formdata.append('avatar',fileinfo)formdata.append('age',12)for(var v of formdata){ console.log(v)}
從FormData對象中刪除一個數(shù)據(jù),使用:
formdata.delete(鍵)
有時候,我們在一個表單中需要上傳多個文件,此時,F(xiàn)ormData中可以不用追加一個文件信息,可以在構(gòu)造FormData對象的時候,將整個表單對象傳入,他會自動識別所有數(shù)據(jù):
<body><form action=""> <input type="text" name="age" value="20"> <input type="file" name="avatar"> <input type="button" value="上傳"></form></body><script>document.querySelector('[type="button"]').onclick = function(){ var formdata = new FormData(document.querySelector('form')); for(var v of formdata){ console.log(v) }}</script>
當(dāng)使用FormData上傳的時候,將FormData對象當(dāng)做數(shù)據(jù)傳入,不需要修改請求頭,瀏覽器會自動修改。
此時已經(jīng)實(shí)現(xiàn)了前后端分離上傳了,但是正常項目中都會有一個預(yù)覽圖片的功能。
我們可以讓后端在實(shí)現(xiàn)上傳后,將上傳以后的文件名稱傳送給前端,讓前端渲染返回的圖片路徑。
但這樣是在上傳以后預(yù)覽的,假設(shè)我們選擇了文件以后,就想看看這個文件是否要上傳,也就是在上傳之前要預(yù)覽的話,還是沒有辦法實(shí)現(xiàn)。
我們可以利用H5提供的FileReader來讀取文件并預(yù)覽,然后決定是否要上傳。
ajax上傳
ajax進(jìn)行上傳后
var xhr = new XMLHttpRequest; xhr.onreadystatechange = function(){ if(xhr.readyState === 4){ if(xhr.status>=200 && xhr.status<300){ var res = xhr.responseText; // console.log(res); if(res==1){ alert('上傳成功') location.reload() } } } } xhr.open('post','2-upload.php')
將文件數(shù)據(jù)放在send中進(jìn)行傳送
需要借助H5提供的構(gòu)造函數(shù)FormData – 用來識別文件信息
var fd = new FormData()
將文件信息放在fd這個對象中 – 用fd的append方法
文件信息在哪里?
var file = document.querySelector('[type="file"]') // console.dir(file); var fileinfo = file.files[0] / 文件信息
append方法,是將文件放入這個對象中,對象就需要鍵值對,參數(shù)1是鍵,參數(shù)2是文件信息
fd.append('avatar',fileinfo)
fd有一個特性,就是直接打印他, 看不到其中的內(nèi)容 需要遍歷才能看到其中的數(shù)據(jù) – 必須使用 for of
for(var value of fd){ console.log(value); }
fd除了能添加文件信息,還可以添加數(shù)據(jù)
fd.append('username',document.querySelector('[name="username"]').value)
上傳文件的時候,利用FormData,里面就有了數(shù)據(jù)和文件信息,其實(shí)最終文件和數(shù)據(jù)以二進(jìn)制數(shù)據(jù)流進(jìn)行傳送的,不需要設(shè)置請求頭,因為ajax會自動調(diào)整
文件數(shù)據(jù)其實(shí)就是fd
php:
現(xiàn)在能打印出數(shù)據(jù),文件存到了臨時目錄中
上傳就是將臨時文件移動到服務(wù)器中
header("content-type:text/html;charset=utf8");echo "<pre>";print_r($_FILES);move_uploaded_file($_FILES['avatar']['tmp_name'],'./upload/'.$_FILES['avatar']['name']);// echo '上傳成功';echo './upload/'.$_FILES['avatar']['name']; echo "<script> alert('上傳成功') location.assign('./1-上傳表單.html')
ajax 上傳前:
當(dāng)文件選擇器中的數(shù)據(jù)發(fā)生了變化就要讀取并預(yù)覽
讀取并預(yù)覽 – 借助H5提供的FileReader – 讀取文件是異步讀取
構(gòu)造函數(shù)需要new
document.querySelector('[type="file"]').onchange = function(){var fr = new FileReader();
readAsArrayBuffer – 將數(shù)據(jù)讀取成一個Buffer數(shù)組
var fileinfo = this.files[0]
參數(shù)要一個文件對象 – 結(jié)果是一個buffer
fr.readAsArrayBuffer(fileinfo)
參數(shù)要一個文件對象 – 結(jié)果是一個二進(jìn)制數(shù)據(jù) – 適用于多媒體文件
fr.readAsBinaryString(fileinfo)
結(jié)果是一個可以當(dāng)做路徑使用的數(shù)據(jù) – base64字符串 – 適用于圖片
fr.readAsDataURL(fileinfo)
在load事件中,讀取完成后獲取讀取出來的數(shù)據(jù)
load事件在讀取完成的時候觸發(fā)
fr.onload = function(){ / result屬性是讀取出來的內(nèi)容 / console.log(fr.result); / 創(chuàng)建img標(biāo)簽 var img = document.createElement('img') img.src = fr.result; document.body.appendChild(img)
FileReader讀取文件
FileReader也是js內(nèi)置的一個構(gòu)造函數(shù),主要功能是用來讀取文件,讀取文件為異步讀取。
var fr = new FileReader() 創(chuàng)建讀取對象// 該對象的讀取方法:fr.readAsDataUrl(文件信息) 將文件讀取為base64字符串 - 通常用于圖片文件,讀取結(jié)果可以作為圖片的src使用 fr.readAsArrayBuffer(文件信息) 將文件讀取為二進(jìn)制數(shù)據(jù)流 - 通常用于多媒體文件 fr.readAsText(文件信息) 將文件讀取為字符串 - 通常用于文檔文件
對象監(jiān)聽的事件:
abort事件:在讀取被中斷的時候觸發(fā)
error事件:讀取發(fā)生錯誤的時候觸發(fā)
load事件:在讀取完成的時候觸發(fā) – 常用語讀取完成后獲取數(shù)據(jù)
loadstart事件:在讀取開始的時候觸發(fā)
loadend事件:在讀取結(jié)束的時候觸發(fā)
progress事件:在讀取過程中觸發(fā)
例:
fr.onload = function(){ 讀取結(jié)果為:對象.result 或 事件對象.target.result console.log(fr.result) 此時這個數(shù)據(jù)就可以作為img的src進(jìn)行圖片預(yù)覽}
base64是指:小寫字母+大寫字母+數(shù)字+加號+等于號 共64個字符
jquery上傳
data位置就直接寫formData就好了
設(shè)置一個content-type為false表示jquery不要設(shè)置請求頭
設(shè)置一個processData為false,表示query不要修改數(shù)據(jù)格式
<form action="javascript:;"> <input type="file" name="avatar"> <input type="text" name="username"> <br> <input type="button" value="上傳"></form>
我們可以在new的時候,將表單元素放在構(gòu)造函數(shù)中 – 默認(rèn)能將表單中的數(shù)據(jù),添加到這個對象中
$('[type="button"]').click(function() var fd = new FormData($('form')[0]) $.ajax({ url:"2-upload.php", method:"post", jquery上傳用 FormData data:fd, contentType:false, 不讓jQuery的ajax修改請求頭 processData:false, 不讓jquery的ajax編碼數(shù)據(jù) success:res=>{ console.log(res); } })})
webWorker
大量運(yùn)算的代碼,可以作為一個異步線程執(zhí)行
需要將這段代碼單獨(dú)放在一個文件中
需要new一個worker對象 – 這個構(gòu)造函數(shù)需要在服務(wù)器環(huán)境中運(yùn)行
woker需要一個事件,當(dāng)文件完成以后獲取里面的數(shù)據(jù)
可以在事件中,接收到文件中導(dǎo)出的數(shù)據(jù)
woker.onmessage = function(e){ 數(shù)據(jù)就在事件對象的data屬性中 console.log(e.data);}
當(dāng)業(yè)務(wù)邏輯需要的計算量比較大的時候,同步代碼會阻塞下面的代碼繼續(xù)執(zhí)行,此時需要將這個大計算量的代碼另外開辟一個新的線程進(jìn)行執(zhí)行,這個線程也是異步執(zhí)行的,但需要將在新線程中執(zhí)行的代碼單獨(dú)放在一個js文件中,使用方式:
var w = new Worker(需要異步執(zhí)行的js文件)
如果在主線程中需要這個線程中返回的數(shù)據(jù),在這個線程中使用postMessage來返回:
postMessage(數(shù)據(jù))
主線程中接收返回出來的數(shù)據(jù):
w.onmessage = function(e){ e.data // 是異步線程中返回出來的數(shù)據(jù)}
離線緩存
離線緩存的作用:在馬上斷網(wǎng)之后,依舊可以訪問到緩存下來的文件。比較雞肋。該技術(shù)在2020年8月已經(jīng)被棄用了。
使用方式:
使用規(guī)則 1. 需要你自定義一個 .manifest 文件 2. 再你書寫 html 文件的時候 => 如果這個 html 文件需要緩存規(guī)則 => 再 html 標(biāo)簽上添加一個 manifest 屬性 => 值就寫你的緩存規(guī)則文件 3. 第一次打開頁面的時候 => 就會按照你書寫的緩存規(guī)則去緩存文件
例:
第一行必須要寫上
CACHE MANIFEST
以注釋的形式書寫一個版本號
app version 1.0
表示你要緩存的文件
CACHE:
./index.html
./css/index.css
表示必須需要網(wǎng)絡(luò)環(huán)境才可以請求的文件
一般我們會書寫 星號(*), 表示除了 CACHE 里面書寫的文件, 其他的去過沒有網(wǎng)絡(luò)環(huán)境就報錯
NETWORK:
*
當(dāng)你再一個離線環(huán)境下訪問一些沒有的頁面的時候
使用一個什么內(nèi)容替代
FALLBACK:
- ./404.html
事件循環(huán)面試題:
<script>console.log(1)setTimeout(()=>{ console.log(2) },0) new Promise(resolve=>{ console.log(3) resolve()}).then(()=>{ console.log(4)})setTimeout(()=>{ console.log(5) new Promise(resolve=>{ console.log(6) setTimeout(()=>{ console.log(7) }) resolve() }).then(()=>{ console.log(8) })},500)new Promise(resolve=>{ console.log(9) resolve()}).then(()=>{ console.log(10) setTimeout(()=>{ console.log(11) },0)})console.log(12)</script>
答案:1 3 9 12 4 10 2 11 5 6 8 7