<address id="ttjl9"></address>

      <noframes id="ttjl9"><address id="ttjl9"><nobr id="ttjl9"></nobr></address>
      <form id="ttjl9"></form>
        <em id="ttjl9"><span id="ttjl9"></span></em>
        <address id="ttjl9"></address>

          <noframes id="ttjl9"><form id="ttjl9"></form>

          首頁

          VUE-多文件斷點續傳、秒傳、分片上傳

          seo達人

          凡是要知其然知其所以然

          文件上傳相信很多朋友都有遇到過,那或許你也遇到過當上傳大文件時,上傳時間較長,且經常失敗的困擾,并且失敗后,又得重新上傳很是煩人。那我們先了解下失敗的原因吧!


          據我了解大概有以下原因:


          服務器配置:例如在PHP中默認的文件上傳大小為8M【post_max_size = 8m】,若你在一個請求體中放入8M以上的內容時,便會出現異常

          請求超時:當你設置了接口的超時時間為10s,那么上傳大文件時,一個接口響應時間超過10s,那么便會被Faild掉。

          網絡波動:這個就屬于不可控因素,也是較常見的問題。

          基于以上原因,聰明的人們就想到了,將文件拆分多個小文件,依次上傳,不就解決以上1,2問題嘛,這便是分片上傳。 網絡波動這個實在不可控,也許一陣大風刮來,就斷網了呢。那這樣好了,既然斷網無法控制,那我可以控制只上傳已經上傳的文件內容,不就好了,這樣大大加快了重新上傳的速度。所以便有了“斷點續傳”一說。此時,人群中有人插了一嘴,有些文件我已經上傳一遍了,為啥還要在上傳,能不能不浪費我流量和時間。喔...這個嘛,簡單,每次上傳時判斷下是否存在這個文件,若存在就不重新上傳便可,于是又有了“秒傳”一說。從此這"三兄弟" 便自行CP,統治了整個文件界。”

          注意文中的代碼并非實際代碼,請移步至github查看代碼

          https://github.com/pseudo-god...


          分片上傳

          HTML

          原生INPUT樣式較丑,這里通過樣式疊加的方式,放一個Button.

           <div class="btns">

             <el-button-group>

               <el-button :disabled="changeDisabled">

                 <i class="el-icon-upload2 el-icon--left" size="mini"></i>選擇文件

                 <input

                   v-if="!changeDisabled"

                   type="file"

                   :multiple="multiple"

                   class="select-file-input"

                   :accept="accept"

                   @change="handleFileChange"

                 />

               </el-button>

               <el-button :disabled="uploadDisabled" @click="handleUpload()"><i class="el-icon-upload el-icon--left" size="mini"></i>上傳</el-button>

               <el-button :disabled="pauseDisabled" @click="handlePause"><i class="el-icon-video-pause el-icon--left" size="mini"></i>暫停</el-button>

               <el-button :disabled="resumeDisabled" @click="handleResume"><i class="el-icon-video-play el-icon--left" size="mini"></i>恢復</el-button>

               <el-button :disabled="clearDisabled" @click="clearFiles"><i class="el-icon-video-play el-icon--left" size="mini"></i>清空</el-button>

             </el-button-group>

             <slot

             

          //data 數據


          var chunkSize = 10 * 1024 * 1024; // 切片大小

          var fileIndex = 0; // 當前正在被遍歷的文件下標


          data: () => ({

             container: {

               files: null

             },

             tempFilesArr: [], // 存儲files信息

             cancels: [], // 存儲要取消的請求

             tempThreads: 3,

             // 默認狀態

             status: Status.wait

           }),

             

          一個稍微好看的UI就出來了。




          選擇文件

          選擇文件過程中,需要對外暴露出幾個鉤子,熟悉elementUi的同學應該很眼熟,這幾個鉤子基本與其一致。onExceed:文件超出個數限制時的鉤子、beforeUpload:文件上傳之前

          fileIndex 這個很重要,因為是多文件上傳,所以定位當前正在被上傳的文件就很重要,基本都靠它


          handleFileChange(e) {

           const files = e.target.files;

           if (!files) return;

           Object.assign(this.$data, this.$options.data()); // 重置data所有數據


           fileIndex = 0; // 重置文件下標

           this.container.files = files;

           // 判斷文件選擇的個數

           if (this.limit && this.container.files.length > this.limit) {

             this.onExceed && this.onExceed(files);

             return;

           }


           // 因filelist不可編輯,故拷貝filelist 對象

           var index = 0; // 所選文件的下標,主要用于剔除文件后,原文件list與臨時文件list不對應的情況

           for (const key in this.container.files) {

             if (this.container.files.hasOwnProperty(key)) {

               const file = this.container.files[key];


               if (this.beforeUpload) {

                 const before = this.beforeUpload(file);

                 if (before) {

                   this.pushTempFile(file, index);

                 }

               }


               if (!this.beforeUpload) {

                 this.pushTempFile(file, index);

               }


               index++;

             }

           }

          },

          // 存入 tempFilesArr,為了上面的鉤子,所以將代碼做了拆分

          pushTempFile(file, index) {

           // 額外的初始值

           const obj = {

             status: fileStatus.wait,

             chunkList: [],

             uploadProgress: 0,

             hashProgress: 0,

             index

           };

           for (const k in file) {

             obj[k] = file[k];

           }

           console.log('pushTempFile -> obj', obj);

           this.tempFilesArr.push(obj);

          }

          分片上傳

          創建切片,循環分解文件即可


           createFileChunk(file, size = chunkSize) {

             const fileChunkList = [];

             var count = 0;

             while (count < file.size) {

               fileChunkList.push({

                 file: file.slice(count, count + size)

               });

               count += size;

             }

             return fileChunkList;

           }

          循環創建切片,既然咱們做的是多文件,所以這里就有循環去處理,依次創建文件切片,及切片的上傳。

          async handleUpload(resume) {

           if (!this.container.files) return;

           this.status = Status.uploading;

           const filesArr = this.container.files;

           var tempFilesArr = this.tempFilesArr;


           for (let i = 0; i < tempFilesArr.length; i++) {

             fileIndex = i;

             //創建切片

             const fileChunkList = this.createFileChunk(

               filesArr[tempFilesArr[i].index]

             );

               

             tempFilesArr[i].fileHash ='xxxx'; // 先不用看這個,后面會講,占個位置

             tempFilesArr[i].chunkList = fileChunkList.map(({ file }, index) => ({

               fileHash: tempFilesArr[i].hash,

               fileName: tempFilesArr[i].name,

               index,

               hash: tempFilesArr[i].hash + '-' + index,

               chunk: file,

               size: file.size,

               uploaded: false,

               progress: 0, // 每個塊的上傳進度

               status: 'wait' // 上傳狀態,用作進度狀態顯示

             }));

             

             //上傳切片

             await this.uploadChunks(this.tempFilesArr[i]);

           }

          }

          上傳切片,這個里需要考慮的問題較多,也算是核心吧,uploadChunks方法只負責構造傳遞給后端的數據,核心上傳功能放到sendRequest方法中

          async uploadChunks(data) {

           var chunkData = data.chunkList;

           const requestDataList = chunkData

             .map(({ fileHash, chunk, fileName, index }) => {

               const formData = new FormData();

               formData.append('md5', fileHash);

               formData.append('file', chunk);

               formData.append('fileName', index); // 文件名使用切片的下標

               return { formData, index, fileName };

             });


           try {

             await this.sendRequest(requestDataList, chunkData);

           } catch (error) {

             // 上傳有被reject的

             this.$message.error('親 上傳失敗了,考慮重試下呦' + error);

             return;

           }


           // 合并切片

           const isUpload = chunkData.some(item => item.uploaded === false);

           console.log('created -> isUpload', isUpload);

           if (isUpload) {

             alert('存在失敗的切片');

           } else {

             // 執行合并

             await this.mergeRequest(data);

           }

          }

          sendReques。上傳這是最重要的地方,也是容易失敗的地方,假設有10個分片,那我們若是直接發10個請求的話,很容易達到瀏覽器的瓶頸,所以需要對請求進行并發處理。


          并發處理:這里我使用for循環控制并發的初始并發數,然后在 handler 函數里調用自己,這樣就控制了并發。在handler中,通過數組API.shift模擬隊列的效果,來上傳切片。

          重試: retryArr 數組存儲每個切片文件請求的重試次數,做累加。比如[1,0,2],就是第0個文件切片報錯1次,第2個報錯2次。為保證能與文件做對應,const index = formInfo.index; 我們直接從數據中拿之前定義好的index。 若失敗后,將失敗的請求重新加入隊列即可。


          關于并發及重試我寫了一個小Demo,若不理解可以自己在研究下,文件地址:https://github.com/pseudo-god... , 重試代碼好像被我弄丟了,大家要是有需求,我再補吧!

             // 并發處理

          sendRequest(forms, chunkData) {

           var finished = 0;

           const total = forms.length;

           const that = this;

           const retryArr = []; // 數組存儲每個文件hash請求的重試次數,做累加 比如[1,0,2],就是第0個文件切片報錯1次,第2個報錯2次


           return new Promise((resolve, reject) => {

             const handler = () => {

               if (forms.length) {

                 // 出棧

                 const formInfo = forms.shift();


                 const formData = formInfo.formData;

                 const index = formInfo.index;

                 

                 instance.post('fileChunk', formData, {

                   onUploadProgress: that.createProgresshandler(chunkData[index]),

                   cancelToken: new CancelToken(c => this.cancels.push(c)),

                   timeout: 0

                 }).then(res => {

                   console.log('handler -> res', res);

                   // 更改狀態

                   chunkData[index].uploaded = true;

                   chunkData[index].status = 'success';

                   

                   finished++;

                   handler();

                 })

                   .catch(e => {

                     // 若暫停,則禁止重試

                     if (this.status === Status.pause) return;

                     if (typeof retryArr[index] !== 'number') {

                       retryArr[index] = 0;

                     }


                     // 更新狀態

                     chunkData[index].status = 'warning';


                     // 累加錯誤次數

                     retryArr[index]++;


                     // 重試3次

                     if (retryArr[index] >= this.chunkRetry) {

                       return reject('重試失敗', retryArr);

                     }


                     this.tempThreads++; // 釋放當前占用的通道


                     // 將失敗的重新加入隊列

                     forms.push(formInfo);

                     handler();

                   });

               }


               if (finished >= total) {

                 resolve('done');

               }

             };


             // 控制并發

             for (let i = 0; i < this.tempThreads; i++) {

               handler();

             }

           });

          }

          切片的上傳進度,通過axios的onUploadProgress事件,結合createProgresshandler方法進行維護

          // 切片上傳進度

          createProgresshandler(item) {

           return p => {

             item.progress = parseInt(String((p.loaded / p.total) * 100));

             this.fileProgress();

           };

          }

          Hash計算

          其實就是算一個文件的MD5值,MD5在整個項目中用到的地方也就幾點。

          秒傳,需要通過MD5值判斷文件是否已存在。

          續傳:需要用到MD5作為key值,當唯一值使用。

          本項目主要使用worker處理,性能及速度都會有很大提升.

          由于是多文件,所以HASH的計算進度也要體現在每個文件上,所以這里使用全局變量fileIndex來定位當前正在被上傳的文件

          執行計算hash


          正在上傳文件


          // 生成文件 hash(web-worker)

          calculateHash(fileChunkList) {

           return new Promise(resolve => {

             this.container.worker = new Worker('./hash.js');

             this.container.worker.postMessage({ fileChunkList });

             this.container.worker.onmessage = e => {

               const { percentage, hash } = e.data;

               if (this.tempFilesArr[fileIndex]) {

                 this.tempFilesArr[fileIndex].hashProgress = Number(

                   percentage.toFixed(0)

                 );

               }


               if (hash) {

                 resolve(hash);

               }

             };

           });

          }

          因使用worker,所以我們不能直接使用NPM包方式使用MD5。需要單獨去下載spark-md5.js文件,并引入


          //hash.js


          self.importScripts("/spark-md5.min.js"); // 導入腳本

          // 生成文件 hash

          self.onmessage = e => {

           const { fileChunkList } = e.data;

           const spark = new self.SparkMD5.ArrayBuffer();

           let percentage = 0;

           let count = 0;

           const loadNext = index => {

             const reader = new FileReader();

             reader.readAsArrayBuffer(fileChunkList[index].file);

             reader.onload = e => {

               count++;

               spark.append(e.target.result);

               if (count === fileChunkList.length) {

                 self.postMessage({

                   percentage: 100,

                   hash: spark.end()

                 });

                 self.close();

               } else {

                 percentage += 100 / fileChunkList.length;

                 self.postMessage({

                   percentage

                 });

                 loadNext(count);

               }

             };

           };

           loadNext(0);

          };

          文件合并

          當我們的切片全部上傳完畢后,就需要進行文件的合并,這里我們只需要請求接口即可

          mergeRequest(data) {

            const obj = {

              md5: data.fileHash,

              fileName: data.name,

              fileChunkNum: data.chunkList.length

            };


            instance.post('fileChunk/merge', obj,

              {

                timeout: 0

              })

              .then((res) => {

                this.$message.success('上傳成功');

              });

          }

          Done: 至此一個分片上傳的功能便已完成

          斷點續傳

          顧名思義,就是從那斷的就從那開始,明確思路就很簡單了。一般有2種方式,一種為服務器端返回,告知我從那開始,還有一種是瀏覽器端自行處理。2種方案各有優缺點。本項目使用第二種。

          思路:已文件HASH為key值,每個切片上傳成功后,記錄下來便可。若需要續傳時,直接跳過記錄中已存在的便可。本項目將使用Localstorage進行存儲,這里我已提前封裝好addChunkStorage、getChunkStorage方法。


          存儲在Stroage的數據




          緩存處理

          在切片上傳的axios成功回調中,存儲已上傳成功的切片


          instance.post('fileChunk', formData, )

           .then(res => {

             // 存儲已上傳的切片下標

          + this.addChunkStorage(chunkData[index].fileHash, index);

             handler();

           })

          在切片上傳前,先看下localstorage中是否存在已上傳的切片,并修改uploaded


             async handleUpload(resume) {

          +      const getChunkStorage = this.getChunkStorage(tempFilesArr[i].hash);

               tempFilesArr[i].chunkList = fileChunkList.map(({ file }, index) => ({

          +        uploaded: getChunkStorage && getChunkStorage.includes(index), // 標識:是否已完成上傳

          +        progress: getChunkStorage && getChunkStorage.includes(index) ? 100 : 0,

          +        status: getChunkStorage && getChunkStorage.includes(index)? 'success'

          +              : 'wait' // 上傳狀態,用作進度狀態顯示

               }));


             }

          構造切片數據時,過濾掉uploaded為true的


          async uploadChunks(data) {

           var chunkData = data.chunkList;

           const requestDataList = chunkData

          +    .filter(({ uploaded }) => !uploaded)

             .map(({ fileHash, chunk, fileName, index }) => {

               const formData = new FormData();

               formData.append('md5', fileHash);

               formData.append('file', chunk);

               formData.append('fileName', index); // 文件名使用切片的下標

               return { formData, index, fileName };

             })

          }

          垃圾文件清理

          隨著上傳文件的增多,相應的垃圾文件也會增多,比如有些時候上傳一半就不再繼續,或上傳失敗,碎片文件就會增多。解決方案我目前想了2種

          前端在localstorage設置緩存時間,超過時間就發送請求通知后端清理碎片文件,同時前端也要清理緩存。

          前后端都約定好,每個緩存從生成開始,只能存儲12小時,12小時后自動清理

          以上2中方案似乎都有點問題,極有可能造成前后端因時間差,引發切片上傳異常的問題,后面想到合適的解決方案再來更新吧。

          Done: 續傳到這里也就完成了。


          秒傳

          這算是最簡單的,只是聽起來很厲害的樣子。原理:計算整個文件的HASH,在執行上傳操作前,向服務端發送請求,傳遞MD5值,后端進行文件檢索。若服務器中已存在該文件,便不進行后續的任何操作,上傳也便直接結束。大家一看就明白

          async handleUpload(resume) {

             if (!this.container.files) return;

             const filesArr = this.container.files;

             var tempFilesArr = this.tempFilesArr;


             for (let i = 0; i < tempFilesArr.length; i++) {

               const fileChunkList = this.createFileChunk(

                 filesArr[tempFilesArr[i].index]

               );


               // hash校驗,是否為秒傳

          +      tempFilesArr[i].hash = await this.calculateHash(fileChunkList);

          +      const verifyRes = await this.verifyUpload(

          +        tempFilesArr[i].name,

          +        tempFilesArr[i].hash

          +      );

          +      if (verifyRes.data.presence) {

          +       tempFilesArr[i].status = fileStatus.secondPass;

          +       tempFilesArr[i].uploadProgress = 100;

          +      } else {

                 console.log('開始上傳切片文件----》', tempFilesArr[i].name);

                 await this.uploadChunks(this.tempFilesArr[i]);

               }

             }

           }

           // 文件上傳之前的校驗: 校驗文件是否已存在

           verifyUpload(fileName, fileHash) {

             return new Promise(resolve => {

               const obj = {

                 md5: fileHash,

                 fileName,

                 ...this.uploadArguments //傳遞其他參數

               };

               instance

                 .post('fileChunk/presence', obj)

                 .then(res => {

                   resolve(res.data);

                 })

                 .catch(err => {

                   console.log('verifyUpload -> err', err);

                 });

             });

           }

          Done: 秒傳到這里也就完成了。

          后端處理

          文章好像有點長了,具體代碼邏輯就先不貼了,除非有人留言要求,嘻嘻,有時間再更新

          Node版

          請前往 https://github.com/pseudo-god... 查看

          JAVA版

          下周應該會更新處理

          PHP版

          1年多沒寫PHP了,抽空我會慢慢補上來

          待完善

          切片的大?。哼@個后面會做出動態計算的。需要根據當前所上傳文件的大小,自動計算合適的切片大小。避免出現切片過多的情況。

          文件追加:目前上傳文件過程中,不能繼續選擇文件加入隊列。(這個沒想好應該怎么處理。)

          更新記錄

          組件已經運行一段時間了,期間也測試出幾個問題,本來以為沒BUG的,看起來BUG都挺嚴重

          BUG-1:當同時上傳多個內容相同但是文件名稱不同的文件時,出現上傳失敗的問題。


          預期結果:第一個上傳成功后,后面相同的問文件應該直接秒傳


          實際結果:第一個上傳成功后,其余相同的文件都失敗,錯誤信息,塊數不對。


          原因:當第一個文件塊上傳完畢后,便立即進行了下一個文件的循環,導致無法及時獲取文件是否已秒傳的狀態,從而導致失敗。


          解決方案:在當前文件分片上傳完畢并且請求合并接口完畢后,再進行下一次循環。


          將子方法都改為同步方式,mergeRequest 和 uploadChunks 方法





          BUG-2: 當每次選擇相同的文件并觸發beforeUpload方法時,若第二次也選擇了相同的文件,beforeUpload方法失效,從而導致整個流程失效。

          原因:之前每次選擇文件時,沒有清空上次所選input文件的數據,相同數據的情況下,是不會觸發input的change事件。


          解決方案:每次點擊input時,清空數據即可。我順帶優化了下其他的代碼,具體看提交記錄吧。


          <input

           v-if="!changeDisabled"

           type="file"

           :multiple="multiple"

           class="select-file-input"

           :accept="accept"

          +  οnclick="f.outerHTML=f.outerHTML"

           @change="handleFileChange"/>

          重寫了暫停和恢復的功能,實際上,主要是增加了暫停和恢復的狀態





          之前的處理邏輯太簡單粗暴,存在諸多問題?,F在將狀態定位在每一個文件之上,這樣恢復上傳時,直接跳過即可





          封裝組件

          寫了一大堆,其實以上代碼你直接復制也無法使用,這里我將此封裝了一個組件。大家可以去github下載文件,里面有使用案例 ,若有用記得隨手給個star,謝謝!

          偷個懶,具體封裝組件的代碼就不列出來了,大家直接去下載文件查看,若有不明白的,可留言。


          組件文檔

          Attribute

          參數 類型 說明 默認 備注

          headers Object 設置請求頭

          before-upload Function 上傳文件前的鉤子,返回false則停止上傳

          accept String 接受上傳的文件類型

          upload-arguments Object 上傳文件時攜帶的參數

          with-credentials Boolean 是否傳遞Cookie false

          limit Number 最大允許上傳個數 0 0為不限制

          on-exceed Function 文件超出個數限制時的鉤子

          multiple Boolean 是否為多選模式 true

          base-url String 由于本組件為內置的AXIOS,若你需要走代理,可以直接在這里配置你的基礎路徑

          chunk-size Number 每個切片的大小 10M

          threads Number 請求的并發數 3 并發數越高,對服務器的性能要求越高,盡可能用默認值即可

          chunk-retry Number 錯誤重試次數 3 分片請求的錯誤重試次數

          Slot

          方法名 說明 參數 備注

          header 按鈕區域 無

          tip 提示說明文字 無

          后端接口文檔:按文檔實現即可

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務






          初學者應該看的 Webpack 完整指南(2020)

          seo達人

          我們應該學習 webpack 嗎 ?

          如今,CLI工具(如create-react-app或Vue -cli)已經為我們抽象了大部分配置,并提供了合理的默認設置。


          即使那樣,了解幕后工作原理還是有好處的,因為我們遲早需要對默認值進行一些調整。


          在本文中中,我們會知道 webpack可以做什么,以及如何配置它以滿足我們的日常需求。


          什么是 webpack?

          作為前端開發人員,我們應該熟悉 module 概念。 你可能聽說過 AMD模塊,UMD,Common JS還有ES模塊。


          webpack是一個模塊綁定器,它對模塊有一個更廣泛的定義,對于webpack來說,模塊是:


          Common JS modules

          AMD modules

          CSS import

          Images url

          ES modules

          webpack 還可以從這些模塊中獲取依賴關系。


          webpack 的最終目標是將所有這些不同的源和模塊類型統一起來,從而將所有內容導入JavaScript代碼,并最生成可以運行的代碼。


          entry

          Webpack的 entry(入口點)是收集前端項目的所有依賴項的起點。 實際上,這是一個簡單的 JavaScript 文件。


          這些依賴關系形成一個依賴關系圖。


          Webpack 的默認入口點(從版本4開始)是src/index.js,它是可配置的。 webpack 可以有多個入口點。


          Output

          output是生成的JavaScript和靜態文件的地方。


          Loaders

          Loaders 是第三方擴展程序,可幫助webpack處理各種文件擴展名。 例如,CSS,圖像或txt文件。


          Loaders的目標是在模塊中轉換文件(JavaScript以外的文件)。 文件成為模塊后,webpack可以將其用作項目中的依賴項。


          Plugins

          插件是第三方擴展,可以更改webpack的工作方式。 例如,有一些用于提取HTML,CSS或設置環境變量的插件。


          Mode

          webpack 有兩種操作模式:開發(development)和生產(production)。 它們之間的主要區別是生產模式自動生成一些優化后的代碼。


          Code splitting

          代碼拆分或延遲加載是一種避免生成較大包的優化技術。


          通過代碼拆分,開發人員可以決定僅在響應某些用戶交互時加載整個JavaScript塊,比如單擊或路由更改(或其他條件)。


          被拆分的一段代碼稱為 chunk。


          Webpack入門

          開始使用webpack時,先創建一個新文件夾,然后進入該文件中,初始化一個NPM項目,如下所示:


          mkdir webpack-tutorial && cd $_


          npm init -y

          接著安裝 webpack,webpack-cli和 webpack-dev-server:


          npm i webpack webpack-cli webpack-dev-server --save-dev

          要運行 webpack,只需要在 package.json 配置如下命令即可:


           "scripts": {

             "dev": "webpack --mode development"

           },

          通過這個腳本,我們指導webpack在開發模式下工作,方便在本地工作。


          Webpack 的第一步

          在開發模式下運行 webpack:


          npm run dev

          運行完后會看到如下錯誤:


          ERROR in Entry module not found: Error: Can't resolve './src'

          webpack 在這里尋找默認入口點src/index.js,所以我們需要手動創建一下,并輸入一些內容:


          mkdir src


          echo 'console.log("Hello webpack!")' > src/index.js

          現在再次運行npm run dev,錯誤就沒有了。 運行的結果生成了一個名為dist/的新文件夾,其中包含一個名為main.js的 JS 文件:


          dist

          └── main.js

          這是我們的第一個webpack包,也稱為output。


          配置 Webpack

          對于簡單的任務,webpack無需配置即可工作,但是很快我們就會遇到問題,一些文件如果沒有指定的 loader 是沒法打包的。所以,我們需要對 webpack進行配置,對于 webpack 的配置是在 webpack.config.js 進行的,所以我們需要創建該文件:


          touch webpack.config.js

          Webpack 用 JavaScript 編寫,并在無頭 JS 環境(例如Node.js)上運行。 在此文件中,至少需要一個module.exports,這是的 Common JS 導出方式:


          module.exports = {

           //

          };

          在webpack.config.js中,我們可以通過添加或修改來改變webpack的行為方式


          entry point

          output

          loaders

          plugins

          code splitting

          例如,要更改入口路徑,我們可以這樣做


          const path = require("path");


          module.exports = {

           entry: { index: path.resolve(__dirname, "source", "index.js") }

          };

          現在,webpack 將在source/index.js中查找要加載的第一個文件。 要更改包的輸出路徑,我們可以這樣做:


          const path = require("path");


          module.exports = {

           output: {

             path: path.resolve(__dirname, "build")

           }

          }

          這樣,webpack將把最終生成包放在build中,而不是dist.(為了簡單起見,在本文中,我們使用默認配置)。


          打包 HTML

          沒有HTML頁面的Web應用程序幾乎沒有用。 要在webpack中使用 HTML,我們需要安裝一個插件html-webpack-plugin:


          npm i html-webpack-plugin --save-dev

          一旦插件安裝好,我們就可以對其進行配置:


          const HtmlWebpackPlugin = require("html-webpack-plugin");

          const path = require("path");


          module.exports = {

           plugins: [

             new HtmlWebpackPlugin({

               template: path.resolve(__dirname, "src", "index.html")

             })

           ]

          };

          這里的意思是讓 webpack,從 src/index.html 加載 HTML 模板。


          html-webpack-plugin的最終目標有兩個:


          加載 html 文件

          它將bundle注入到同一個文件中

          接著,我們需要在 src/index.html 中創建一個簡單的 HTML 文件:


          <!DOCTYPE html>

          <html lang="en">

          <head>

             <meta charset="UTF-8">

             <title>Webpack tutorial</title>

          </head>

          <body>


          </body>

          </html>

          稍后,我們會運行這個程序。


          webpack development server

          在本文第一部分中,我們安裝了webpack-dev-server。如果你忘記安裝了,現在可以運行下面命令安裝一下:


          npm i webpack-dev-server --save-dev

          webpack-dev-server 可以讓開發更方便,不需要改動了文件就去手動刷新文件。 配置完成后,我們可以啟動本地服務器來提供文件。


          要配置webpack-dev-server,請打開package.json并添加一個 “start” 命令:


          "scripts": {

           "dev": "webpack --mode development",

           "start": "webpack-dev-server --mode development --open",

          },

          有了 start 命令,我們來跑一下:


          npm start

          運行后,默認瀏覽器應打開。 在瀏覽器的控制臺中,還應該看到一個 script 標簽,引入的是我們的 main.js。


          clipboard.png


          使用 webpack loader

          Loader是第三方擴展程序,可幫助webpack處理各種文件擴展名。 例如,有用于 CSS,圖像或 txt 文件的加載程序。


          下面是一些 loader 配置介紹:


          module.exports = {

           module: {

             rules: [

               {

                 test: /\.filename$/,

                 use: ["loader-b", "loader-a"]

               }

             ]

           },

           //

          };

          相關配置以module 關鍵字開始。 在module內,我們在rules內配置每個加載程序組或單個加載程序。


          對于我們想要作為模塊處理的每個文件,我們用test和use配置一個對象


          {

             test: /\.filename$/,

             use: ["loader-b", "loader-a"]

          }

          test 告訴 webpack “嘿,將此文件名視為一個模塊”。 use 定義將哪些 loaders 應用于些打包的文件。


          打包 CSS

          要 在webpack 中打包CSS,我們需要至少安裝兩個 loader。Loader 對于幫助 webpack 了解如何處理.css文件是必不可少的。


          要在 webpack 中測試 CSS,我們需要在 src 下創建一個style.css文件:


          h1 {

             color: orange;

          }

          另外在 src/index.html 添加 h1 標簽


          <!DOCTYPE html>

          <html lang="en">

          <head>

             <meta charset="UTF-8">

             <title>Webpack tutorial</title>

          </head>

          <body>

          <h1>Hello webpack!</h1>

          </body>

          </html>

          最后,在src/index.js 中加載 CSS:


          在測試之前,我們需要安裝兩個 loader:


          css-loader: 解析 css 代碼中的 url、@import語法像import和require一樣去處理css里面引入的模塊

          style-loader:幫我們直接將css-loader解析后的內容掛載到html頁面當中

          安裝 loader:


          npm i css-loader style-loader --save-dev

          然后在webpack.config.js中配置它們


          const HtmlWebpackPlugin = require("html-webpack-plugin");

          const path = require("path");


          module.exports = {

           module: {

             rules: [

               {

                 test: /\.css$/,

                 use: ["style-loader", "css-loader"]

               }

             ]

           },

           plugins: [

             new HtmlWebpackPlugin({

               template: path.resolve(__dirname, "src", "index.html")

             })

           ]

          };

          現在,如果你運行npm start,會看到樣式表加載在HTML的頭部:


          clipboard.png


          一旦CSS Loader 就位,我們還可以使用MiniCssExtractPlugin提取CSS文件


          Webpack Loader 順序很重要!

          在webpack中,Loader 在配置中出現的順序非常重要。以下配置無效:


          //


          module.exports = {

           module: {

             rules: [

               {

                 test: /\.css$/,

                 use: ["css-loader", "style-loader"]

               }

             ]

           },

           //

          };

          此處,“style-loader”出現在 “css-loader” 之前。 但是style-loader用于在頁面中注入樣式,而不是用于加載實際的CSS文件。


          相反,以下配置有效:


          module.exports = {

           module: {

             rules: [

               {

                 test: /\.css$/,

                 use: ["style-loader", "css-loader"]

               }

             ]

           },

           //

          };

          webpack loaders 是從右到左執行的。


          打包 sass

          要在 webpack 中測試sass,同樣,我們需要在 src 目錄下創建一個 style.scss 文件:


          @import url("https://fonts.googleapis.com/css?family=Karla:weight@400;700&display=swap");


          $font: "Karla", sans-serif;

          $primary-color: #3e6f9e;


          body {

           font-family: $font;

           color: $primary-color;

          }

          另外,在src/index.html中添加一些 Dom 元素:


          <!DOCTYPE html>

          <html lang="en">

          <head>

             <meta charset="UTF-8">

             <title>Webpack tutorial</title>

          </head>

          <body>

           <h1>Hello webpack!</h1>

           <p>Hello sass!</p>

          </body>

          </html>

          最后,將 sass 文件加載到src/index.js中:


          import "./style.scss";

          console.log("Hello webpack!");

          在測試之前,我們需要安裝幾個 loader:


          sass-loader:加載 SASS / SCSS 文件并將其編譯為 CSS

          css-loader: 解析 css 代碼中的 url、@import語法像import和require一樣去處理css里面引入的模塊

          style-loader:幫我們直接將css-loader解析后的內容掛載到html頁面當中

          安裝 loader:


          npm i css-loader style-loader sass-loader sass --save-dev

          然后在webpack.config.js中配置它們:


          const HtmlWebpackPlugin = require("html-webpack-plugin");

          const path = require("path");


          module.exports = {

           module: {

             rules: [

               {

                 test: /\.scss$/,

                 use: ["style-loader", "css-loader", "sass-loader"]

               }

             ]

           },

           plugins: [

             new HtmlWebpackPlugin({

               template: path.resolve(__dirname, "src", "index.html")

             })

           ]

          };

          注意loader的出現順序:首先是sass-loader,然后是css-loader,最后是style-loader。


          現在,運行npm start,你應該會在HTML的頭部看到加載的樣式表:


          clipboard.png


          打包現代 JavaScrip

          webpack 本身并不知道如何轉換JavaScript代碼。 該任務已外包給babel的第三方 loader,特別是babel-loader。


          babel是一個JavaScript編譯器和“編譯器”。 babel 可以將現代JS(es6, es7...)轉換為可以在(幾乎)任何瀏覽器中運行的兼容代碼。


          同樣,要使用它,我們需要安裝一些 Loader:


          babel-core :把 js 代碼分析成 ast ,方便各個插件分析語法進行相應的處理

          babel-preset-env:將現代 JS 編譯為ES5

          babel-loader :用于 webpack

          引入依賴關系


          npm i @babel/core babel-loader @babel/preset-env --save-dev

          接著,創建一個新文件babel.config.json配置babel,內容如下:


          {

           "presets": [

             "@babel/preset-env"

           ]

          }

          最后在配置一下 webpack :


          const HtmlWebpackPlugin = require("html-webpack-plugin");

          const path = require("path");


          module.exports = {

           module: {

             rules: [

               {

                 test: /\.scss$/,

                 use: ["style-loader", "css-loader", "sass-loader"]

               },

               {

                 test: /\.js$/,

                 exclude: /node_modules/,

                 use: ["babel-loader"]

               }

             ]

           },

           plugins: [

             new HtmlWebpackPlugin({

               template: path.resolve(__dirname, "src", "index.html")

             })

           ]

          };

          要測試轉換,可以在 src/index.js中編寫一些現代語法:


          import "./style.scss";

          console.log("Hello webpack!");


          const fancyFunc = () => {

           return [1, 2];

          };


          const [a, b] = fancyFunc();

          現在運行npm run dev來查看dist中轉換后的代碼。 打開 dist/main.js并搜索“fancyFunc”:


          \n\nvar fancyFunc = function fancyFunc() {\n return [1, 2];\n};\n\nvar _fancyFunc = fancyFunc(),\n _fancyFunc2 = _slicedToArray(_fancyFunc, 2),\n a = _fancyFunc2[0],\n b = _fancyFunc2[1];\n\n//# sourceURL=webpack:///./src/index.js?"

          沒有babel,代碼將不會被轉譯:


          \n\nconsole.log(\"Hello webpack!\");\n\nconst fancyFunc = () => {\n return [1, 2];\n};\n\nconst [a, b] = fancyFunc();\n\n\n//# sourceURL=webpack:///./src/index.js?");

          注意:即使沒有babel,webpack也可以正常工作。 僅在執行 ES5 代碼時才需要進行代碼轉換過程。


          在 Webpack 中使用 JS 的模塊

          webpack 將整個文件視為模塊。 但是,請不要忘記它的主要目的:加載ES模塊。


          ECMAScript模塊(簡稱ES模塊)是一種JavaScript代碼重用的機制,于2015年推出,一經推出就受到前端開發者的喜愛。在2015之年,JavaScript 還沒有一個代碼重用的標準機制。多年來,人們對這方面的規范進行了很多嘗試,導致現在有多種模塊化的方式。


          你可能聽說過AMD模塊,UMD,或CommonJS,這些沒有孰優孰劣。最后,在ECMAScript 2015中,ES 模塊出現了。


          我們現在有了一個“正式的”模塊系統。


          要在 webpack 使用 ES module ,首先創建 src/common/usersAPI.js 文件:


          const ENDPOINT = "https://jsonplaceholder.typicode.com/users/";


          export function getUsers() {

           return fetch(ENDPOINT)

             .then(response => {

               if (!response.ok) throw Error(response.statusText);

               return response.json();

             })

             .then(json => json);

          }

          在 src/index.js中,引入上面的模塊:


          import { getUsers } from "./common/usersAPI";

          import "./style.scss";

          console.log("Hello webpack!");


          getUsers().then(json => console.log(json));

          生產方式

          如前所述,webpack有兩種操作模式:開發(development )和(production)。 到目前為止,我們僅在開發模式下工作。


          在開發模式中,為了便于代碼調試方便我們快速定位錯誤,不會壓縮混淆源代碼。相反,在生產模式下,webpac k進行了許多優化:


          使用 TerserWebpackPlugin 進行縮小以減小 bundle 的大小

          使用ModuleConcatenationPlugin提升作用域

          在生產模式下配 置webpack,請打開 package.json 并添加一個“ build” 命令:


          現在運行 npm run build,webpack 會生成一個壓縮的包。


          Code splitting

          代碼拆分(Code splitting)是指針對以下方面的優化技術:


          避免出現一個很大的 bundle

          避免重復的依賴關系

          webpack 社區考慮到應用程序的初始 bundle 的最大大小有一個限制:200KB。


          在 webpack 中有三種激活 code splitting 的主要方法:


          有多個入口點

          使用 optimization.splitChunks 選項

          動態導入

          第一種基于多個入口點的技術適用于較小的項目,但是從長遠來看它是不可擴展的。這里我們只關注第二和第三種方式。


          Code splitting 與 optimization.splitChunks

          考慮一個使用Moment.js 的 JS 應用程序,Moment.js是流行的時間和日期JS庫。


          在項目文件夾中安裝該庫:


          npm i moment

          現在清除src/index.js的內容,并引入 moment 庫:


          import moment from "moment";

          運行 npm run build 并查看控制的輸出內容:


          main.js 350 KiB 0 [emitted] [big] main

          整個 moment 庫都綁定到了 main.js 中這樣是不好的。借助optimization.splitChunks,我們可以從主包中移出moment.js。


          要使用它,需要在 webpack.config.js 添加 optimization 選項:


          const HtmlWebpackPlugin = require("html-webpack-plugin");

          const path = require("path");


          module.exports = {

           module: {

           // ...

           },

           optimization: {

             splitChunks: { chunks: "all" }

           },

           // ...

          };

          運行npm run build 并查看運行結果:


                 main.js   5.05 KiB       0  [emitted]         main

          vendors~main.js    346 KiB       1  [emitted]  [big]  vendors~main

          現在,我們有了一個帶有moment.js 的vendors?main.js,而主入口點的大小更合理。


          注意:即使進行代碼拆分,moment.js仍然是一個體積較大的庫。 有更好的選擇,如使用luxon或date-fns。


          Code splitting 與 動態導入

          Code splitting的一種更強大的技術使用動態導入來有條件地加載代碼。 在ECMAScript 2020中提供此功能之前,webpack 提供了動態導入。


          這種方法在 Vue 和 React 之類的現代前端庫中得到了廣泛使用(React有其自己的方式,但是概念是相同的)。


          Code splitting 可用于:


          模塊級別

          路由級別

          例如,你可以有條件地加載一些 JavaScript 模塊,以響應用戶的交互(例如單擊或鼠標移動)。 或者,可以在響應路由更改時加載代碼的相關部分。


          要使用動態導入,我們先清除src/index.html,并寫入下面的內容:


          <!DOCTYPE html>

          <html lang="en">

          <head>

             <meta charset="UTF-8">

             <title>Dynamic imports</title>

          </head>

          <body>

          <button id="btn">Load!</button>

          </body>

          </html>

          在 src/common/usersAPI.js中:


          const ENDPOINT = "https://jsonplaceholder.typicode.com/users/";


          export function getUsers() {

           return fetch(ENDPOINT)

             .then(response => {

               if (!response.ok) throw Error(response.statusText);

               return response.json();

             })

             .then(json => json);

          }

          在 src/index.js 中


          const btn = document.getElementById("btn");


          btn.addEventListener("click", () => {

           //

          });

          如果運行npm run start查看并單擊界面中的按鈕,什么也不會發生。


          現在想象一下,我們想在某人單擊按鈕后加載用戶列表。 “原生”的方法可以使用靜態導入從src/common /usersAPI.js加載函數:


          import { getUsers } from "./common/usersAPI";


          const btn = document.getElementById("btn");


          btn.addEventListener("click", () => {

           getUsers().then(json => console.log(json));

          });

          問題在于ES模塊是靜態的,這意味著我們無法在運行時更改導入的內容。


          通過動態導入,我們可以選擇何時加載代碼


          const getUserModule = () => import("./common/usersAPI");


          const btn = document.getElementById("btn");


          btn.addEventListener("click", () => {

           getUserModule().then(({ getUsers }) => {

             getUsers().then(json => console.log(json));

           });

          });

          這里我們創建一個函數來動態加載模塊


          const getUserModule = () => import("./common/usersAPI");

          現在,當你第一次使用npm run start加載頁面時,會看到控制臺中已加載 js 包:


          clipboard.png


          現在,僅在單擊按鈕時才加載/common/usersAPI:


          clipboard.png


          對應的 chunk 是 0.js


          通過在導入路徑前面加上魔法注釋/ * webpackChunkName:“ name_here” * /,可以更改塊名稱:


          const getUserModule = () =>

           import(/* webpackChunkName: "usersAPI" */ "./common/usersAPI");


          const btn = document.getElementById("btn");


          btn.addEventListener("click", () => {

           getUserModule().then(({ getUsers }) => {

             getUsers().then(json => console.log(json));

           });

          });

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務

          iview按需引入,ie11不兼容,報無效字符問題解決

          seo達人

          準備工作

          //借助插件

          npm install babel-plugin-import --save-dev


          // .babelrc

          {

           "plugins": [["import", {

             "libraryName": "view-design",

             "libraryDirectory": "src/components"

           }]]

          }

          在main.js中引入

          import "view-design/dist/styles/iview.css";

          import { Button, Table } from "view-design";

          const viewDesign = {

          Button: Button,

          Table: Table

          };

          Object.keys(viewDesign).forEach(element => {

          Vue.component(element, viewDesign[element]);

          });

          先用google瀏覽器打開正常,以上操作猛如虎,IE瀏覽器打開250,好了不廢話,下面是解決方案


          解決方案

          //vue.config.js中配置

          chainWebpack: config => {

             //解決iview 按需引入babel轉換問題

            config.module

               .rule("view-design")  //  我目前用的是新版本的iview ,舊版本的iview,用iview代替view-design

               .test(/view-design.src.*?js$/)

               .use("babel")

               .loader("babel-loader")

               .end();

          }

          問題原因

          為什么會有如上問題呢? 這個就和babel轉換問題有關了,按需引入時,那些組件里js文件未進行babel轉換或轉換不徹底就被引入了,ie11對es6+的語法支持是很差的,所以以上方法就是讓引入文件前就對view-design的src下的所有js文件進行babel轉換,舉一反三,當按需引入第三方框架時出現這個問題,都可用這方法解決了,只要把規則和正則中view-design進行替換。


          延伸擴展

          //全局引入

          import ViewUI from "view-design";

          Vue.use(ViewUI);

          import "view-design/dist/styles/iview.css";

          tips:在全局引入時,一定要記住不要在.babelrc文件里配置按需導入,會導致沖突

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務

          如何用 JavaScript 來解析 URL

          seo達人

          統一資源定位符,縮寫為URL,是對網絡資源(網頁、圖像、文件)的引用。URL指定資源位置和檢索資源的機制(http、ftp、mailto)。


          舉個例子,這里是這篇文章的 URL 地址:


          https://dmitripavlutin.com/parse-url-javascript

          很多時候你需要獲取到一段 URL 的某個組成部分。它們可能是 hostname(例如 dmitripavlutin.com),或者 pathname(例如 /parse-url-javascript)。


          一個方便的用于獲取 URL 組成部分的辦法是通過 URL() 構造函數。


          在這篇文章中,我將給大家展示一段 URL 的結構,以及它的主要組成部分。


          接著,我會告訴你如何使用 URL() 構造函數來輕松獲取 URL 的組成部分,比如 hostname,pathname,query 或者 hash。


          1. URL 結構

          一圖勝千言。不需要過多的文字描述,通過下面的圖片你就可以理解一段 URL 的各個組成部分:


          image


          2. URL() 構造函數

          URL() 構造函數允許我們用它來解析一段 URL:


          const url = new URL(relativeOrAbsolute [, absoluteBase]);

          參數 relativeOrAbsolute 既可以是絕對路徑,也可以是相對路徑。如果第一個參數是相對路徑的話,那么第二個參數 absoluteBase 則必傳,且必須為第一個參數的絕對路徑。


          舉個例子,讓我們用一個絕對路徑的 URL 來初始化 URL() 函數:


          const url = new URL('http://example.com/path/index.html');


          url.href; // => 'http://example.com/path/index.html'

          或者我們可以使用相對路徑和絕對路徑:


          const url = new URL('/path/index.html', 'http://example.com');


          url.href; // => 'http://example.com/path/index.html'

          URL() 實例中的 href 屬性返回了完整的 URL 字符串。


          在新建了 URL() 的實例以后,你可以用它來訪問前文圖片中的任意 URL 組成部分。作為參考,下面是 URL() 實例的接口列表:


          interface URL {

           href:     USVString;

           protocol: USVString;

           username: USVString;

           password: USVString;

           host:     USVString;

           hostname: USVString;

           port:     USVString;

           pathname: USVString;

           search:   USVString;

           hash:     USVString;


           readonly origin: USVString;

           readonly searchParams: URLSearchParams;


           toJSON(): USVString;

          }

          上述的 USVString 參數在 JavaScript 中會映射成字符串。


          3. Query 字符串

          url.search 可以獲取到 URL 當中 ? 后面的 query 字符串:


          const url = new URL(

           'http://example.com/path/index.html?message=hello&who=world'

          );


          url.search; // => '?message=hello&who=world'

          如果 query 參數不存在,url.search 默認會返回一個空字符串 '':


          const url1 = new URL('http://example.com/path/index.html');

          const url2 = new URL('http://example.com/path/index.html?');


          url1.search; // => ''

          url2.search; // => ''

          3.1 解析 query 字符串

          相比于獲得原生的 query 字符串,更實用的場景是獲取到具體的 query 參數。


          獲取具體 query 參數的一個簡單的方法是利用 url.searchParams 屬性。這個屬性是 URLSearchParams 的實例。


          URLSearchParams 對象提供了許多用于獲取 query 參數的方法,如get(param),has(param)等。


          下面來看個例子:


          const url = new URL(

           'http://example.com/path/index.html?message=hello&who=world'

          );


          url.searchParams.get('message'); // => 'hello'

          url.searchParams.get('missing'); // => null

          url.searchParams.get('message') 返回了 message 這個 query 參數的值——hello。


          如果使用 url.searchParams.get('missing') 來獲取一個不存在的參數,則得到一個 null。


          4. hostname

          url.hostname 屬性返回一段 URL 的 hostname 部分:


          const url = new URL('http://example.com/path/index.html');


          url.hostname; // => 'example.com'

          5. pathname

          url. pathname 屬性返回一段 URL 的 pathname 部分:


          const url = new URL('http://example.com/path/index.html?param=value');


          url.pathname; // => '/path/index.html'

          如果這段 URL 不含 path,則該屬性返回一個斜杠 /:


          const url = new URL('http://example.com/');


          url.pathname; // => '/'

          6. hash

          最后,我們可以通過 url.hash 屬性來獲取 URL 中的 hash 值:


          const url = new URL('http://example.com/path/index.html#bottom');


          url.hash; // => '#bottom'

          當 URL 中的 hash 不存在時,url.hash 屬性會返回一個空字符串 '':


          const url = new URL('http://example.com/path/index.html');


          url.hash; // => ''

          7. URL 校驗

          當使用 new URL() 構造函數來新建實例的時候,作為一種副作用,它同時也會對 URL 進行校驗。如果 URL 不合法,則會拋出一個 TypeError。


          舉個例子,http ://example.com 是一段非法 URL,因為它在 http 后面多寫了一個空格。


          讓我們用這個非法 URL 來初始化 URL() 構造函數:


          try {

           const url = new URL('http ://example.com');

          } catch (error) {

           error; // => TypeError, "Failed to construct URL: Invalid URL"

          }

          因為 http ://example.com 是一段非法 URL,跟我們想的一樣,new URL() 拋出了一個 TypeError。


          8. 修改 URL

          除了獲取 URL 的組成部分以外,像 search,hostname,pathname 和 hash 這些屬性都是可寫的——這也意味著你可以修改 URL。


          舉個例子,讓我們把一段 URL 從 red.com 修改成 blue.io:


          const url = new URL('http://red.com/path/index.html');


          url.href; // => 'http://red.com/path/index.html'


          url.hostname = 'blue.io';


          url.href; // => 'http://blue.io/path/index.html'

          注意,在 URL() 實例中只有 origin 和 searchParams 屬性是只讀的,其他所有的屬性都是可寫的,并且會修改原來的 URL。


          9. 總結

          URL() 構造函數是 JavaScript 中的一個能夠很方便地用于解析(或者校驗)URL 的工具。


          new URL(relativeOrAbsolute [, absoluteBase]) 中的第一個參數接收 URL 的絕對路徑或者相對路徑。當第一個參數是相對路徑時,第二個參數必傳且必須為第一個參數的基路徑。


          在新建 URL() 的實例以后,你就能很輕易地獲得 URL 當中的大部分組成部分了,比如:


          url.search 獲取原生的 query 字符串

          url.searchParams 通過 URLSearchParams 的實例去獲取具體的 query 參數

          url.hostname獲取 hostname

          url.pathname 獲取 pathname

          url.hash 獲取 hash 值

          那么你最愛用的解析 URL 的 JavaScript 工具又是什么呢?

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務

          TypeScript 運行時類型檢查指南

          seo達人

          為什么需要額外的類型檢查?

          TypeScript 只在編譯期執行靜態類型檢查!實際運行的是從 TypeScript 編譯的 JavaScript,這些生成的 JavaScript 對類型一無所知。編譯期靜態類型檢查在代碼庫內部能發揮很大作用,但對不合規范的輸入(比如,從 API 處接收的輸入)無能為力。


          運行時檢查的嚴格性

          至少需要和編譯期檢查一樣嚴格,否則就失去了編譯期檢查提供的保證。

          如有必要,可以比編譯期檢查更嚴格,例如,年齡需要大于等于 0。

          運行時類型檢查策略

          定制代碼手動檢查

          靈活

          可能比較枯燥,容易出錯

          容易和實際代碼脫節

          使用校驗庫手動檢查

          比如使用 joi:


          import Joi from "@hapi/joi"const schema = Joi.object({    firstName: Joi.string().required(),    lastName: Joi.string().required(),    age: Joi.number().integer().min(0).required()});

          靈活

          容易編寫

          容易和實際代碼脫節

          手動創建 JSON Schema

          例如:


          {  "$schema": "http://json-schema.org/draft-07/schema#",  "required": [    "firstName",    "lastName",    "age"  ],  "properties": {    "firstName": {      "type": "string"    },    "lastName": {      "type": "string"    },    "age": {      "type": "integer",      "minimum": 0    }  }}

          使用標準格式,有大量庫可以校驗。

          JSON 很容易存儲和復用。

          可能會很冗長,手寫 JSON Schema 可能會很枯燥。

          需要確保 Schema 和代碼同步更新。

          自動創建 JSON Schema

          基于 TypeScript 代碼生成 JSON Schema

          -- 比如 typescript-json-schema 這個工具就可以做到這一點(同時支持作為命令行工具使用和通過代碼調用)。

          -- 需要確保 Schema 和代碼同步更新。

          基于 JSON 輸入示例生成

          -- 沒有使用已經在 TypeScript 代碼中定義的類型信息。

          -- 如果提供的 JSON 輸入示例和實際輸入不一致,可能導致錯誤。

          -- 仍然需要確保 Schema 和代碼同步更新。

          轉譯

          例如使用 ts-runtime。


          這種方式會將代碼轉譯成功能上等價但內置運行時類型檢查的代碼。


          比如,下面的代碼:


          interface Person {    firstName: string;    lastName: string;    age: number;}const test: Person = {    firstName: "Foo",    lastName: "Bar",    age: 55}

          會被轉譯為:


          import t from "ts-runtime/lib";const Person = t.type(    "Person",    t.object(        t.property("firstName", t.string()),        t.property("lastName", t.string()),        t.property("age", t.number())    ));const test = t.ref(Person).assert({    firstName: "Foo",    lastName: "Bar",    age: 55});

          這一方式的缺陷是無法控制在何處進行運行時檢查(我們只需在輸入輸出的邊界處進行運行時類型檢查)。


          順便提一下,這是一個實驗性的庫,不建議在生產環境使用。


          運行時類型派生靜態類型

          比如使用 io-ts 這個庫。


          這一方式下,我們定義運行時類型,TypeScript 會根據我們定義的運行時類型推斷出靜態類型。


          運行時類型示例:


          import t from "io-ts";const PersonType = t.type({  firstName: t.string,  lastName: t.string,  age: t.refinement(t.number, n => n >= 0, 'Positive')})

          從中提取相應的靜態類型:


          interface Person extends t.TypeOf<typeof PersonType> {}

          以上類型等價于:


          interface Person {    firstName: string;    lastName: string;    age: number;}

          類型總是同步的。

          io-ts 很強大,比如支持遞歸類型。

          需要將類型定義為 io-ts 運行時類型,這在定義類時不適用:

          -- 有一種變通的辦法是使用 io-ts 定義一個接口,然后讓類實現這個接口。然而,這意味著每次給類增加屬性的時候都要更新 io-ts 類型。

          不容易復用接口(比如前后端之間使用同一接口),因為這些接口是 io-ts 類型而不是普通的 TypeScript 類型。

          基于裝飾器的類校驗

          比如使用 class-validator 這個庫。


          基于類屬性的裝飾器。

          和 Java 的 JSR-380 Bean Validation 2.0 (比如 Hibernate Validator 就實現了這一標準)很像。

          -- 此類 Java EE 風格的庫還有 typeorm (ORM 庫,類似 Java 的 JPA)和 routing-controllers (用于定義 API,類似 Java 的 JAX-RS)。

          代碼示例:


          import { plainToClass } from "class-transformer";import {     validate, IsString, IsInt, Min } from "class-validator";class Person {    @IsString()    firstName: string;    @IsString()    lastName: string;    @IsInt()    @Min(0)    age: number;}const input: any = {    firstName: "Foo",    age: -1};const inputAsClassInstance = plainToClass(    Person, input as Person);validate(inputAsClassInstance).then(errors => {    // 錯誤處理代碼});

          類型總是同步的。

          需要對類進行檢查時很有用。

          可以用來檢查接口(定義一個實現接口的類)。

          注意:class-validator 用于具體的類實例。在上面的代碼中,我們使用它的姊妹庫 class-transformer 將普通輸入轉換為 Person 實例。轉換過程本身不進行任何類型檢查。

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務

          幫助你優化網站,提高頁面速度的10種基礎方法

          seo達人

          如今,Page Speed(頁面速度)的意義非凡。


          自從Google改變Googlebot's的算法以高度支持快速,適合移動設備的網站以來,擁有快速網站變得越來越重要。如果這還不夠好,用戶通常會花更少的時間,轉化率也會更低,你的網站體驗越慢,用戶的轉化率就越低。


          什么是Page Speed

          Page Speed是將內容完全加載到網頁上所花費的時間。


          對于任何給定的用戶來說,頁面緩慢的原因可能有很多,你的用戶可能正在火車上,通過信號弱的隧道,或者他們的互聯網速度很慢。


          通過遵循最佳實踐,我們至少可以通過確保我們已經做了最好的工作來緩解問題。


          現在你知道它是什么了,下面我就來教你如何提高頁面速度。


          注意:這些是按難度順序列出的。在某個時候,你將需要開發人員來幫助優化你的網站。


          1.使用CDN



          CDN是內容傳輸網絡的縮寫。使用CDN可以讓你有效地訪問全球數百臺小服務器,這些服務器為你提供網站的副本,大大減少了你的網站獲取時間。如果你沒有使用CDN,你的網站的每一個請求(包括圖片、CSS和JavaScript)都會被緩慢地傳送到你的服務器上。


          根據HTTPArchive中的4.68億個請求,48%的請求不是來自CDN。那是超過2.24億的請求,如果他們花幾分鐘的時間給自己的網站添加一個CDN,速度可能會超過50%。


          一定要檢查你的CDN配置是否正確——在你的CDN中緩存丟失意味著CDN必須向你的源服務器請求資源,這就違背了使用CDN的初衷!所以,你的CDN必須要有一個正確的配置。


          2.啟用GZIP壓縮



          在一些CDN上,GZIP壓縮只是一個標有 "啟用壓縮 "的復選框。這大概會減少一半的文件大小,你的用戶需要下載文件才能使用你的網站,你的用戶會因此而喜歡你。


          3.使用較小的圖像

          這意味著既要降低分辨率(例如,攝像頭的輸出從4000x3000像素減少到網絡的1000x750),又要通過壓縮文件來減小尺寸。


          如果你的網站使用WordPress,則有一些插件會在你上傳圖片時自動為你執行此操作。


          在撰寫博客文章時,我個人使用TinyJPG壓縮圖像。


          https://tinyjpg.com/


          4.減少頁面發出的請求數

          目標是減少加載頁面頂部部分所需的請求數量。


          這里有兩種思維方式,你可以:


          通過刪除花哨的動畫或不能改善網站體驗的圖像,減少整個頁面上的請求數量。

          或者,你可以通過使用延遲加載來推遲優先級不高的加載內容。

          5.盡可能避免重定向



          重定向會大大降低網站速度。使用響應式CSS并從一個域為你的網站提供服務,而不是為移動用戶提供特殊的子域。


          有些重定向是不可避免的,比如 www-> 根域 或 根域 ->www,但你的大部分流量不應該經歷重定向來查看你的網站。


          6.減少到第一個字節的時間

          到第一個字節的時間是指你的瀏覽器在發出資源請求后,從服務器接收到第一個字節的數據所花費的時間。


          有兩個部分:


          在服務器上花費的時間

          發送數據所花費的時間

          你可以通過優化你的服務器端渲染、數據庫查詢、API調用、負載平衡、你的應用程序的實際代碼以及服務器的負載本身(特別是如果你使用的是廉價的虛擬主機——這將影響你的網站的性能),來改善你在服務器上花費的時間。


          你可以使用CDN大大減少發送數據所花費的時間。


          7.減少并刪除阻止渲染的JavaScript

          外部腳本(特別是那些用于營銷的外部腳本)往往會寫得很差,會阻止你的頁面加載,直到它運行完畢。


          你可以通過將外部腳本標記為異步來減少這種影響:


          <script async src="https://example.com/external.js"></script>

          你還可以延遲加載市場營銷腳本,直到用戶開始滾動為止:


          window.addEventListener(

           'scroll',

           () =>

             setTimeout(() => {

               // 在此插入營銷片段

             }, 1000),

           { once: true }

          );

          8.縮小CSS和JS

          Minifying是指使用工具來刪除空格、換行符和縮短變量名。通常情況下,這將作為構建過程的一部分自動完成。


          要縮小JavaScript,請查看UglifyJS。


          http://lisperator.net/uglifyjs/


          要縮小CSS,請查看cssnano。


          9.刪除未使用的CSS

          自Chrome 59(2017年4月發布)以來,在Chrome DevTools中可以看到未使用的JS和CSS。


          要看這個,打開DevTools,顯示控制臺抽屜(就是點擊Esc時出現的那個煩人的東西),點擊左下角的三個小點,打開 "Coverage",就可以看到。


          點擊帶有重新加載圖標的按鈕將刷新頁面,并審核CSS和JS的使用情況。


          在Google Chrome瀏覽器中審核初始頁面時,外觀如下所示:




          10.定期跟蹤網站速度

          在你的網站速度變慢的瞬間,修復網站速度問題就會容易得多。除此之外,如果你把檢查網站速度作為一種習慣,那么修復網站速度慢的問題就會變成一件小得多的事情。


          有免費的工具可以監視你網站的速度,其中的兩個是WebPageTest和Google Lighthouse。這些工具的缺點是你需要記住在進行更改之前和之后都必須運行它們。


          PerfBeacon是一項服務(由本文的作者創建),該服務定期運行Google Lighthouse,并讓你隨時跟蹤網站的速度。


          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 平面設計服務

          JavaScript中的緩存API

          seo達人

          了解如何使用JavaScript中的Cache API緩存資源。


          Cache API允許服務工作者對要緩存的資源(HTML頁面、CSS、JavaScript文件、圖片、JSON等)進行控制。通過Cache API,服務工作者可以緩存資源以供脫機使用,并在以后檢索它們。


          檢測Cache支持

          檢查 caches 對象在 window 中是否可用。


          let isCacheSupported = 'caches' in window;

          caches 是 CacheStorage 的一個實例。


          創建/初始化Cache

          我們可以使用 open 方法創建一個具有 name 的緩存,這將返回 promise。如果緩存已經存在,則不會創建新的緩存。


          caches.open('cacheName').then( cache => {

          });

          你不能訪問為其他源(域)設置的緩存。

          你正在創建的緩存將為你的域創建。

          你可以為同一個域添加多個緩存,可以通過 caches.keys() 訪問。

          將項目添加到緩存

          可以使用三種方法 add,addAll,set 來緩存資源。 add() 和 addAll() 方法自動獲取資源并對其進行緩存,而在 set 方法中,我們將獲取數據并設置緩存。


          add

          let cacheName = 'userSettings';

          let url = '/api/get/usersettings';

          caches.open(cacheName).then( cache => {

            cache.add(url).then( () => {

                console.log("Data cached ")

             });

          });

          在上面的代碼中,內部對 /api/get/usersettings url的請求已發送到服務器,一旦接收到數據,響應將被緩存。


          addAll

          addAll 接受URL數組,并在緩存所有資源時返回Promise。


          let urls = ['/get/userSettings?userId=1', '/get/userDetails'];

          caches.open(cacheName).then( cache => {

          cache.addAll(urls).then( () => {

                console.log("Data cached ")

             });

          });

          Cache.add/Cache.addAll 不緩存 Response.status 值不在200范圍內的響應,Cache.put 可以讓你存儲任何請求/響應對。


          put

          put 為當前的 Cache 對象添加一個key/value對,在 put 中,我們需要手動獲取請求并設置值。


          注意:put() 將覆蓋先前存儲在高速緩存中與請求匹配的任何鍵/值對。


          let cacheName = 'userSettings';

          let url = '/api/get/userSettings';

          fetch(url).then(res => {

           return caches.open(cacheName).then(cache => {

             return cache.put(url, res);

           })

          })

          從緩存中檢索

          使用 cache.match() 可以得到存儲到URL的 Response。


          const cacheName = 'userSettings'

          const url = '/api/get/userSettings'

          caches.open(cacheName).then(cache => {

           cache.match(url).then(settings => {

             console.log(settings);

           }

          });

          settings 是一個響應對象,它看起來像


          Response {

           body: (...),

           bodyUsed: false,

           headers: Headers,

           ok: true,

           status: 200,

           statusText: "OK",

           type: "basic",

           url: "https://test.com/api/get/userSettings"

          }

          檢索緩存中的所有項目

          cache 對象包含 keys 方法,這些方法將擁有當前緩存對象的所有url。


          caches.open(cacheName).then( (cache) => {

           cache.keys().then((arrayOfRequest) => {

               console.log(arrayOfRequest); // [Request,  Request]

           });

          });

          arrayOfRequest是一個Request對象數組,其中包含有關請求的所有詳細信息。


          檢索所有緩存

          caches.keys().then(keys => {

           // keys是一個數組,其中包含鍵的列表

          })

          從緩存中刪除項目

          可以對 cache 對象使用 delete 方法來刪除特定的緩存請求。


          let cacheName = userSettings;

          let urlToDelete = '/api/get/userSettings';

          caches.open(cacheName).then(cache => {

           cache.delete(urlToDelete)

          })

          完全刪除緩存

          caches.delete(cacheName).then(() => {

            console.log('Cache successfully deleted!');

          })

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 平面設計服務

          Vue中使用裝飾器,我是認真的

          seo達人

          作為一個曾經的Java coder, 當我第一次看到js里面的裝飾器(Decorator)的時候,就馬上想到了Java中的注解,當然在實際原理和功能上面,Java的注解和js的裝飾器還是有很大差別的。本文題目是Vue中使用裝飾器,我是認真的,但本文將從裝飾器的概念開發聊起,一起來看看吧。


          通過本文內容,你將學到以下內容:


          了解什么是裝飾器

          在方法使用裝飾器

          在class中使用裝飾器

          在Vue中使用裝飾器

          本文首發于公眾號【前端有的玩】,不想當咸魚,想要換工作,關注公眾號,帶你每日一起刷大廠面試題,關注 === 大廠offer。

          什么是裝飾器

          裝飾器是ES2016提出來的一個提案,當前處于Stage 2階段,關于裝飾器的體驗,可以點擊 https://github.com/tc39/proposal-decorators查看詳情。裝飾器是一種與類相關的語法糖,用來包裝或者修改類或者類的方法的行為,其實裝飾器就是設計模式中裝飾者模式的一種實現方式。不過前面說的這些概念太干了,我們用人話來翻譯一下,舉一個例子。


          在日常開發寫bug過程中,我們經常會用到防抖和節流,比如像下面這樣


          class MyClass {

           follow = debounce(function() {

             console.log('我是子君,關注我哦')

           }, 100)

          }


          const myClass = new MyClass()

          // 多次調用只會輸出一次

          myClass.follow()

          myClass.follow()

          上面是一個防抖的例子,我們通過debounce函數將另一個函數包起來,實現了防抖的功能,這時候再有另一個需求,比如希望在調用follow函數前后各打印一段日志,這時候我們還可以再開發一個log函數,然后繼續將follow包裝起來


          /**

          * 最外層是防抖,否則log會被調用多次

          */

          class MyClass {

           follow = debounce(

             log(function() {

               console.log('我是子君,關注我哦')

             }),

             100

           )

          }

          上面代碼中的debounce和log兩個函數,本質上是兩個包裝函數,通過這兩個函數對原函數的包裝,使原函數的行為發生了變化,而js中的裝飾器的原理就是這樣的,我們使用裝飾器對上面的代碼進行改造


          class MyClass {

           @debounce(100)

           @log

           follow() {

             console.log('我是子君,關注我哦')

           }

          }

          裝飾器的形式就是 @ + 函數名,如果有參數的話,后面的括號里面可以傳參


          在方法上使用裝飾器

          裝飾器可以應用到class上或者class里面的屬性上面,但一般情況下,應用到class屬性上面的場景會比較多一些,比如像上面我們說的log,debounce等等,都一般會應用到類屬性上面,接下來我們一起來具體看一下如何實現一個裝飾器,并應用到類上面。在實現裝飾器之前,我們需要先了解一下屬性描述符


          了解一下屬性描述符

          在我們定義一個對象里面的屬性的時候,其實這個屬性上面是有許多屬性描述符的,這些描述符標明了這個屬性能不能修改,能不能枚舉,能不能刪除等等,同時ECMAScript將這些屬性描述符分為兩類,分別是數據屬性和訪問器屬性,并且數據屬性與訪問器屬性是不能共存的。


          數據屬性

          數據屬性包含一個數據值的位置,在這個位置可以讀取和寫入值。數據屬性包含了四個描述符,分別是


          configurable

          表示能不能通過delete刪除屬性,能否修改屬性的其他描述符特性,或者能否將數據屬性修改為訪問器屬性。當我們通過let obj = {name: ''}聲明一個對象的時候,這個對象里面所有的屬性的configurable描述符的值都是true


          enumerable

          表示能不能通過for in或者Object.keys等方式獲取到屬性,我們一般聲明的對象里面這個描述符的值是true,但是對于class類里面的屬性來說,這個值是false


          writable

          表示能否修改屬性的數據值,通過將這個修改為false,可以實現屬性只讀的效果。


          value

          表示當前屬性的數據值,讀取屬性值的時候,從這里讀?。粚懭雽傩灾档臅r候,會寫到這個位置。


          訪問器屬性

          訪問器屬性不包含數據值,他們包含了getter與setter兩個函數,同時configurable與enumerable是數據屬性與訪問器屬性共有的兩個描述符。


          getter

          在讀取屬性的時候調用這個函數,默認這個函數為undefined


          setter

          在寫入屬性值的時候調用這個函數,默認這個函數為undefined


          了解了這六個描述符之后,你可能會有幾個疑問: 我如何去定義修改這些屬性描述符?這些屬性描述符與今天的文章主題有什么關系?接下來是揭曉答案的時候了。


          使用Object.defineProperty

          了解過vue2.0雙向綁定原理的同學一定知道,Vue的雙向綁定就是通過使用Object.defineProperty去定義數據屬性的getter與setter方法來實現的,比如下面有一個對象


          let obj = {

           name: '子君',

           officialAccounts: '前端有的玩'

          }

          我希望這個對象里面的用戶名是不能被修改的,用Object.defineProperty該如何定義呢?


          Object.defineProperty(obj,'name', {

           // 設置writable 是 false, 這個屬性將不能被修改

           writable: false

          })

          // 修改obj.name

          obj.name = "君子"

          // 打印依然是子君

          console.log(obj.name)

          通過Object.defineProperty可以去定義或者修改對象屬性的屬性描述符,但是因為數據屬性與訪問器屬性是互斥的,所以一次只能修改其中的一類,這一點需要注意。


          定義一個防抖裝飾器

          裝飾器本質上依然是一個函數,不過這個函數的參數是固定的,如下是防抖裝飾器的代碼


          /**

          *@param wait 延遲時長

          */

          function debounce(wait) {

           return function(target, name, descriptor) {

             descriptor.value = debounce(descriptor.value, wait)

           }

          }

          // 使用方式

          class MyClass {

           @debounce(100)

           follow() {

             console.log('我是子君,我的公眾號是 【前端有的玩】,關注有驚喜哦')

           }

          }

          我們逐行去分析一下代碼


          首先我們定義了一個 debounce函數,同時有一個參數wait,這個函數對應的就是在下面調用裝飾器時使用的@debounce(100)

          debounce函數返回了一個新的函數,這個函數即裝飾器的核心,這個函數有三個參數,下面逐一分析


          target: 這個類屬性函數是在誰上面掛載的,如上例對應的是MyClass類

          name: 這個類屬性函數的名稱,對應上面的follow

          descriptor: 這個就是我們前面說的屬性描述符,通過直接descriptor上面的屬性,即可實現屬性只讀,數據重寫等功能

          然后第三行 descriptor.value = debounce(descriptor.value, wait), 前面我們已經了解到,屬性描述符上面的value對應的是這個屬性的值,所以我們通過重寫這個屬性,將其用debounce函數包裝起來,這樣在函數調用follow時實際調用的是包裝后的函數

          通過上面的三步,我們就實現了類屬性上面可使用的裝飾器,同時將其應用到了類屬性上面


          在class上使用裝飾器

          裝飾器不僅可以應用到類屬性上面,還可以直接應用到類上面,比如我希望可以實現一個類似Vue混入那樣的功能,給一個類混入一些方法屬性,應該如何去做呢?


          // 這個是要混入的對象

          const methods = {

           logger() {

             console.log('記錄日志')

           }

          }


          // 這個是一個登陸登出類

          class Login{

           login() {}

           logout() {}

          }

          如何將上面的methods混入到Login中,首先我們先實現一個類裝飾器


          function mixins(obj) {

           return function (target) {

             Object.assign(target.prototype, obj)  

           }

          }


          // 然后通過裝飾器混入

          @mixins(methods)

          class Login{

           login() {}

           logout() {}

          }

          這樣就實現了類裝飾器。對于類裝飾器,只有一個參數,即target,對應的就是這個類本身。


          了解完裝飾器,我們接下來看一下如何在Vue中使用裝飾器。


          在Vue中使用裝飾器

          使用ts開發Vue的同學一定對vue-property-decorator不會感到陌生,這個插件提供了許多裝飾器,方便大家開發的時候使用,當然本文的中點不是這個插件。其實如果我們的項目沒有使用ts,也是可以使用裝飾器的,怎么用呢?


          配置基礎環境

          除了一些老的項目,我們現在一般新建Vue項目的時候,都會選擇使用腳手架vue-cli3/4來新建,這時候新建的項目已經默認支持了裝飾器,不需要再配置太多額外的東西,如果你的項目使用了eslint,那么需要給eslint配置以下內容。


           parserOptions: {

             ecmaFeatures:{

               // 支持裝飾器

               legacyDecorators: true

             }

           }

          使用裝飾器

          雖然Vue的組件,我們一般書寫的時候export出去的是一個對象,但是這個并不影響我們直接在組件中使用裝飾器,比如就拿上例中的log舉例。


          function log() {

           /**

            * @param target 對應 methods 這個對象

            * @param name 對應屬性方法的名稱

            * @param descriptor 對應屬性方法的修飾符

            */

           return function(target, name, descriptor) {

             console.log(target, name, descriptor)

             const fn = descriptor.value

             descriptor.value = function(...rest) {

               console.log(`這是調用方法【${name}】前打印的日志`)

               fn.call(this, ...rest)

               console.log(`這是調用方法【${name}】后打印的日志`)

             }

           }

          }


          export default {

           created() {

             this.getData()

           },

           methods: {

             @log()

             getData() {

               console.log('獲取數據')

             }

           }

          }

          看了上面的代碼,是不是發現在Vue中使用裝飾器還是很簡單的,和在class的屬性上面使用的方式一模一樣,但有一點需要注意,在methods里面的方法上面使用裝飾器,這時候裝飾器的target對應的是methods。


          除了在methods上面可以使用裝飾器之外,你也可以在生命周期鉤子函數上面使用裝飾器,這時候target對應的是整個組件對象。


          一些常用的裝飾器

          下面小編羅列了幾個小編在項目中常用的幾個裝飾器,方便大家使用


          1. 函數節流與防抖

          函數節流與防抖應用場景是比較廣的,一般使用時候會通過throttle或debounce方法對要調用的函數進行包裝,現在就可以使用上文說的內容將這兩個函數封裝成裝飾器, 防抖節流使用的是lodash提供的方法,大家也可以自行實現節流防抖函數哦


          import { throttle, debounce } from 'lodash'

          /**

          * 函數節流裝飾器

          * @param {number} wait 節流的毫秒

          * @param {Object} options 節流選項對象

          * [options.leading=true] (boolean): 指定調用在節流開始前。

          * [options.trailing=true] (boolean): 指定調用在節流結束后。

          */

          export const throttle =  function(wait, options = {}) {

           return function(target, name, descriptor) {

             descriptor.value = throttle(descriptor.value, wait, options)

           }

          }


          /**

          * 函數防抖裝飾器

          * @param {number} wait 需要延遲的毫秒數。

          * @param {Object} options 選項對象

          * [options.leading=false] (boolean): 指定在延遲開始前調用。

          * [options.maxWait] (number): 設置 func 允許被延遲的最大值。

          * [options.trailing=true] (boolean): 指定在延遲結束后調用。

          */

          export const debounce = function(wait, options = {}) {

           return function(target, name, descriptor) {

             descriptor.value = debounce(descriptor.value, wait, options)

           }

          }

          封裝完之后,在組件中使用


          import {debounce} from '@/decorator'


          export default {

           methods:{

             @debounce(100)

             resize(){}

           }

          }

          2. loading

          在加載數據的時候,為了個用戶一個友好的提示,同時防止用戶繼續操作,一般會在請求前顯示一個loading,然后在請求結束之后關掉loading,一般寫法如下


          export default {

           methods:{

             async getData() {

               const loading = Toast.loading()

               try{

                 const data = await loadData()

                 // 其他操作

               }catch(error){

                 // 異常處理

                 Toast.fail('加載失敗');

               }finally{

                 loading.clear()

               }  

             }

           }

          }

          我們可以把上面的loading的邏輯使用裝飾器重新封裝,如下代碼


          import { Toast } from 'vant'


          /**

          * loading 裝飾器

          * @param {*} message 提示信息

          * @param {function} errorFn 異常處理邏輯

          */

          export const loading =  function(message = '加載中...', errorFn = function() {}) {

           return function(target, name, descriptor) {

             const fn = descriptor.value

             descriptor.value = async function(...rest) {

               const loading = Toast.loading({

                 message: message,

                 forbidClick: true

               })

               try {

                 return await fn.call(this, ...rest)

               } catch (error) {

                 // 在調用失敗,且用戶自定義失敗的回調函數時,則執行

                 errorFn && errorFn.call(this, error, ...rest)

                 console.error(error)

               } finally {

                 loading.clear()

               }

             }

           }

          }

          然后改造上面的組件代碼


          export default {

           methods:{

             @loading('加載中')

             async getData() {

               try{

                 const data = await loadData()

                 // 其他操作

               }catch(error){

                 // 異常處理

                 Toast.fail('加載失敗');

               }  

             }

           }

          }

          3. 確認框

          當你點擊刪除按鈕的時候,一般都需要彈出一個提示框讓用戶確認是否刪除,這時候常規寫法可能是這樣的


          import { Dialog } from 'vant'


          export default {

           methods: {

             deleteData() {

               Dialog.confirm({

                 title: '提示',

                 message: '確定要刪除數據,此操作不可回退。'

               }).then(() => {

                 console.log('在這里做刪除操作')

               })

             }

           }

          }

          我們可以把上面確認的過程提出來做成裝飾器,如下代碼


          import { Dialog } from 'vant'


          /**

          * 確認提示框裝飾器

          * @param {*} message 提示信息

          * @param {*} title 標題

          * @param {*} cancelFn 取消回調函數

          */

          export function confirm(

           message = '確定要刪除數據,此操作不可回退。',

           title = '提示',

           cancelFn = function() {}

          ) {

           return function(target, name, descriptor) {

             const originFn = descriptor.value

             descriptor.value = async function(...rest) {

               try {

                 await Dialog.confirm({

                   message,

                   title: title

                 })

                 originFn.apply(this, rest)

               } catch (error) {

                 cancelFn && cancelFn(error)

               }

             }

           }

          }

          然后再使用確認框的時候,就可以這樣使用了


          export default {

           methods: {

             // 可以不傳參,使用默認參數

             @confirm()

             deleteData() {

               console.log('在這里做刪除操作')

             }

           }

          }

          是不是瞬間簡單多了,當然還可以繼續封裝很多很多的裝飾器,因為文章內容有限,暫時提供這三個。


          裝飾器組合使用

          在上面我們將類屬性上面使用裝飾器的時候,說道裝飾器可以組合使用,在Vue組件上面使用也是一樣的,比如我們希望在確認刪除之后,調用接口時候出現loading,就可以這樣寫(一定要注意順序)


          export default {

           methods: {

             @confirm()

             @loading()

             async deleteData() {

               await delete()

             }

           }

          }

          本節定義的裝飾器,均已應用到這個項目中 https://github.com/snowzijun/vue-vant-base, 這是一個基于Vant開發的開箱即用移動端框架,你只需要fork下來,無需做任何配置就可以直接進行業務開發,歡迎使用,喜歡麻煩給一個star。

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 平面設計服務




          10 個最佳 CSS 動畫庫

          seo達人

          目錄

          1. Animista

          2. Animate CSS

          3. Vivify

          4. Magic Animations CSS3

          5. cssanimation.io

          6. Angrytools

          7. Hover.css

          8. WickedCSS

          9. Three Dots

          10. CSShake


          1.Animista

          網站地址:http://animista.net/

          網站描述:在線生成 css 動畫




          Animista是一個在線動畫生成器,同時也是一個動畫庫,它為我們提供了以下功能


          1. 選擇不同的動畫

          我們可以選擇想要的動畫類型(例如entrance/exist),除了可以選擇某個動畫(例如,scale-in)外,甚至還可以為該動畫選擇不同的展示效果(例如: scale-in-right)。




          2. 定制

          Animista還提供了一個功能,允許我們定制動畫的某些部分,比如


          duration

          delay

          direction

          更好的是,可以選擇要設置動畫的對象:




          3. 生成CSS代碼

          選擇適合自己需要的動畫后,我們可以直接從網站上獲取代碼,甚者可以進行壓縮:




          4. 下載代碼

          另一個好用的功能是,可以把自己收藏自己喜歡的動畫,然后一起下載下來, 或者,我們也可以選擇將這些動畫的代碼復制到一起。




          2. Animate CSS

          網站地址:http://daneden.github.io/anim...


          網站描述:齊全的CSS3動畫庫




          想必這個不用介紹,大部分人都知道了。Animate CSS 可能是最著名的動畫庫之一。這里簡要介紹一下它的用法:


          1. 用法

          首先,必須在總需要動畫元素上添加類animated ,然后是動畫的名字。


          <div class="animated slideInLeft"></div>

          如果我們想讓動畫一直持續,可以添加infinite類。


          通過 JS 來添加動畫:


          document.querySelector('.my-element').classList.add('animated', 'slideInLeft')

          通過 JQ 來添加動畫:


          $(".my-element").addClass("animated slideInLeft")

          2. 其它功能

          Animate CSS提供了一些基本的類來控制動畫的延遲和速度。


          delay


          可以添加 delay 類來延遲動畫的播放。


          <div class="animated slideInLeft delay-{1-5}"><div>

          speed


          我們還可以通過添加如下列出的類之一來控制動畫速度。


          類名 速度時間

          show 2s

          slower 3s

          fast 800ms

          faster 500ms

          <div class="animated slideInLeft slow|slower|fast|faster"><div>


          3. Vivify

          網站地址: http://vivify.mkcreative.cz/


          網站描述: 一個更加豐富css動畫庫




          Vivify 是一個動畫庫,可以看作是Animate CSS的增強版。它們的工作方式完全相同,有Animate CSS的大多數類且還擴展了一些。


          <div class="vivify slideInLeft"></div>

          使用 JS 方式:


          document.querySelector('.my-element').classList.add('vivify', 'slideInLeft')

          使用 JQ 方式:


          $(".my-element").addClass("vivify slideInLeft")

          與Animate CSS一樣,Vivify 還提供了一些類來控制動畫的持續時間和延遲。


          延遲和持續時間類在以下間隔中可用:


          <div class="delay|duration-{100|150|200|250...1000|1250|1500|1750...10750}"></div>

          4. Magic Animations CSS3

          網站地址: https://www.minimamente.com/p...


          網站描述: Magic CSS3 Animations 是 CSS3 動畫的包,伴有特殊的效果,用戶可以自由的在 web 項目中使用。




          這個動畫庫有一些非常漂亮和流暢的動畫,特別是3D的。沒什么好說的,自己去嘗試。


          <div class="magictime fadeIn"></div>

          使用 JS 方式:


          document.querySelector('.my-element').classList.add('magictime', 'fadeIn')

          使用 JQ 方式:


          $(".my-element").addClass("magictime fadeIn")

          5. cssanimation.io



          網站地址: http://cssanimation.io/index....


          cssanimation.io是一大堆不同動畫的集合,總共大概有200個,這很強大。如果你連在這里都沒有找到你所需的動畫,那么在其它也將很難找到。


          它的工作原理與 Animista 類似。例如,可以選擇一個動畫并直接從站點獲取代碼,或者也可以下載整個庫。




          用法


          將cssanimation {animation_name}添加到指定的元素上。


          <div class="cssanimation fadeIn"></div>

          使用 JS


          document.querySelector('.my-element').classList.add('cssanimation','fadeIn')

          使用 JQ


          $(".my-element").addClass("cssanimation fadeIn")

          還可以添加 infinite 類,這樣動畫就可以循環播放。


          <div class="cssanimation fadeIn infinite"></div>

             

          此外,cssanimation.io還為我們提供了動漫字母的功能。使用這個需要引入letteranimation.js文件,然后將le {animation_name}添加到我們的文本元素中。


          <div class="cssanimation leSnake"></div>

          要使字母按順序產生動畫,添加sequence類,要使它們隨機產生動畫,添加random類。


          <div class="cssanimation leSnake {sequence|random}"></div>

          Sequence



          Random




          6.Angrytools

          網站地址: https://angrytools.com/css/an...


          如果使用不同的生成器,Angrytools實際上是一個集合,其中還包括CSS動畫生成器。


          它可能不像Animista那么復雜,但我覺得這個也很不錯。這個站點還提供了一些自定義動畫的特性,比如動畫的持續時間或延遲。


          但是我喜歡的是,我們可以在其展示時間軸上添加自定義的keyframes,然后可以直接在其中編寫代碼。 另外,也可以編輯現有的。




          當我們完成的時候,可以得到完整的動畫代碼,也可以下載它。


          7.Hover.css

          網站地址: http://ianlunn.github.io/Hover/

          網站描述: 純CSS3鼠標滑過效果動畫庫


          Hover.css是許多CSS動畫的集合,與上面的動畫不同,每次將元素懸停時都會觸發。


          一組CSS3支持的懸停效果,可應用于鏈接、按鈕、徽標、SVG和特色圖像等。

          ** 用法


          它非常簡單:只需將類的名稱添加到元素中,比如


          <button class="hvr-fade">Hover me!</button>

          8.WickedCSS

          網站地址: http://kristofferandreasen.gi...


          WickedCSS是一個小的CSS動畫庫,它沒有太多的動畫變體,但至少有很大的變化。 其中大多數是我們已經熟悉的基礎知識,但它們確實很干凈。


          它的用法很簡單,只需將動畫的名稱添加到元素中即可。


          <div class="bounceIn"></div>

          ** 使用 JS


          document.querySelector('.my-element').classList.add('bounceIn')

          ** 使用 JQ


          $(".my-element").addClass("bounceIn")






          9.Three Dots

          網站地址: https://nzbin.github.io/three...




          Three Dots是一組CSS加載動畫,它由三個點組成,而這些點僅由單個元素組成。


          ** 用法


          只需創建一個div元素,并添加動畫的名稱


          <div class="dot-elastic"></div>


          10.CSShake

          網站地址: https://elrumordelaluz.github...




          顧名思義,CSShake是一個CSS動畫庫,其中包含不同類型的震動動畫。


          ** 用法


          將shake {animation name}添加到元素中。


          <div class="shake shake-hard"></div>

          使用 JS


          document.querySelector('.my-element').classList.add('shake','shake-hard')

          使用 JQ


          $(".my-element").addClass("shake shake-hard")

          人才們的 【三連】 就是小智不斷分享的最大動力,如果本篇博客有任何錯誤和建議,歡迎人才們留言,最后,謝謝大家的觀看。


          代碼部署后可能存在的BUG沒法實時知道,事后為了解決這些BUG,花了大量的時間進行log 調試,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug。


          原文:https://dev.to/weeb/10-of-the...


          交流

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務




          你現在可以使用的10個JavaScript代碼段

          seo達人

          毫無疑問,JavaScript是Web開發中最流行的編程語言之一。無論您使用的是React,Vue還是Angular,都只是JavaScript。圍繞JS展開了廣泛而重要的生態系統,提供了無數的框架和庫,可幫助你更快地開發應用程序。


          但是有時候最好退一步,嘗試了解如何在沒有庫的情況下做事??纯聪旅娴拇a片段,以優雅的方式解決簡單的問題,并在日常項目情況下使用這些知識或為編碼面試做準備。


          1.反轉字符串

          在此示例中,我們使用擴展運算符(…),Array的reverse方法和String的join方法來反轉給定的字符串。


          const reverseString = string => [...string].reverse().join('');


          // 例子

          reverseString('javascript'); // 'tpircsavaj'

          reverseString('good'); // 'doog'

          2.計算數字的階乘

          要計算給定數字的階乘,我們使用箭頭函數和嵌套三元運算符。


          const factoriaOfNumber = number => number < 0 ? (() => {

           throw new TypeError('No negative numbers please');

          })()

          : number <=1

          ? 1

          : number * factoriaOfNumber(number -1);


          // 例子

          factoriaOfNumber(4); // 24

          factoriaOfNumber(8); // 40320

          3.將數字轉換為數字數組

          在此示例中,我們使用擴展運算符(…),Array的map方法和 parseInt 函數將給定的數字轉換為一個單數的數組。


          const convertToArray = number => [...`${number}`].map(el => parseInt(el));


          // 例子

          convertToArray(5678); // [5, 6, 7, 8]

          convertToArray(123456789); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

          4.檢查數字是否為2的冪

          這很簡單。我們檢查該數字不是偽造的,并使用按位AND運算符(&)來確定數字是否為2的冪。


          const isNumberPowerOfTwo = number => !!number && (number & (number - 1)) == 0;


          // 例子

          isNumberPowerOfTwo(100); // false

          isNumberPowerOfTwo(128); // true

          5.從對象創建鍵值對數組

          在此示例中,我們使用Object中的keys方法和Array中的map方法來映射Object的鍵并創建鍵/值對數組。


          const keyValuePairsToArray = object => Object.keys(object).map(el => [el, object[el]]);


          // 例子

          keyValuePairsToArray({ Better: 4, Programming: 2 });

          // [ ['Better', 4], ['Programming', 2] ]

          keyValuePairsToArray({ x: 1, y: 2, z: 3 });

          // [ ['x', 1], ['y', 2], ['z', 3] ]

          6.返回數組中的[Number]個最大元素

          為了從數組中返回最大元素,我們使用了一個箭頭函數,該函數獲取數組和我們希望函數返回的元素數。我們使用擴展運算符(…)以及Array中的sort和slice方法。請注意,如果我們不提供第二個參數,則 number 的默認值為 1,因此僅返回一個最大元素。


          const maxElementsFromArray = (array, number = 1) => [...array].sort((x, y) => y - x).slice(0, number);


          // 例子

          maxElementsFromArray([1,2,3,4,5]); // [5]

          maxElementsFromArray([7,8,9,10,10],2); // [10, 10]

          7.檢查數組中的所有元素是否相等

          在這個簡短的示例中,我們使用Array中的every方法檢查數組中的所有元素是否相等。我們基本上檢查每個元素是否等于數組中的第一個元素。


          const elementsAreEqual = array => array.every(el => el === array[0]);


          // 例子

          elementsAreEqual([9,8,7,6,5]); // false

          elementsAreEqual([4,4,4,4,4]); // true

          8.返回兩個數的平均值

          在此示例中,我們使用了擴展運算符(…)和Array中的reduce方法來返回兩個給定數字或一個數組的平均值。


          const averageOfTwoNumbers = (...numbers) => numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0) / numbers.length;


          // 例子

          averageOfTwoNumbers(...[6,7,8]); // 7

          averageOfTwoNumbers(6,7,8,9); // 7.5

          9.返回兩個或多個數字的總和

          要返回兩個或多個給定數字或一個數組的總和,我們再次使用擴展運算符(…)和Array中的reduce方法。


          const sumOfNumbers = (...array) => [...array].reduce((accumulator, currentValue) => accumulator + currentValue, 0);


          // 例子

          sumOfNumbers(5,6,7,8,9.10); // 45

          sumOfNumbers(...[1,2,3,4,5,6,7,8,9,10]); // 50

          10.返回數字數組的冪集

          在最后一個示例中,我們要返回數字數組的冪集。因此,我們使用Array中的reduce,map和concat方法。


          const powersetOfArray = array => array.reduce((accumulator, currentValue) => accumulator.concat(accumulator.map(el => [currentValue].concat(el))), [[]]);


          // 例子

          powersetOfArray([4, 2]); // [[], [4], [2], [2, 4]]

          powersetOfArray([1, 2, 3]); /

          // [[], [1], [2], [2, 1], [3], [3, 1], [3, 2], [3, 2, 1]]

          如你所見,使用JavaScript和一些ES6魔術來解決這些任務并不總是困難的。

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務



          日歷

          鏈接

          個人資料

          藍藍設計的小編 http://www.syprn.cn

          存檔

          亚洲va欧美va天堂v国产综合