<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>

          首頁

          有趣的Canvas,你值得擁有!

          seo達人

          Canvas 是 HTML5 提供的一個用于展示繪圖效果的標簽. Canvas 原意為畫布, 在 HTML 頁面中用于展示繪圖效果. 最早 Canvas 是蘋果提出的一個方案, 今天已經在大多數瀏覽器中實現。


          canvas 的使用領域


          游戲

          大數據可視化數據

          banner 廣告

          多媒體

          模擬仿真

          遠程操作

          圖形編輯

          判斷瀏覽器是否支持 canvas 標簽


          var canvas = document.getElementById('canvas')

          if (canvas.getContext) {

          console.log('你的瀏覽器支持Canvas!')

          } else {

          console.log('你的瀏覽器不支持Canvas!')

          }

          canvas 的基本用法

          1、使用 canvas 標簽, 即可在頁面中開辟一格區域,可以設置其寬高,寬高為 300 和 150


          <canvas></canvas>

          2、獲取 dom 元素 canvas


          canvas 本身不能繪圖. 是使用 JavaScript 來完成繪圖. canvas 對象提供了各種繪圖用的 api。


          var cas = document.querySelector('canvas')

          3、通過 cas 獲取上下文對象(畫布對象!)


          var ctx = cas.getContext('2d')

          4、通過 ctx 開始畫畫(設置起點 設置終點 連線-描邊 )


          ctx.moveTo(10, 10)

          ctx.lineTo(100, 100)

          ctx.stroke()

          繪制線條

          設置開始位置: context.moveTo( x, y )

          設置終點位置: context.lineTo( x, y )

          描邊繪制: context.stroke()

          填充繪制: context.fill()

          閉合路徑: context.closePath()

          canvas 還可以設置線條的相關屬性,如下:


          CanvasRenderingContext2D.lineWidth 設置線寬.

          CanvasRenderingContext2D.strokeStyle 設置線條顏色.

          CanvasRenderingContext2D.lineCap 設置線末端類型,'butt'( 默認 ), 'round', 'square'.

          CanvasRenderingContext2D.lineJoin 設置相交線的拐點, 'miter'(默認),'round', 'bevel',

          CanvasRenderingContext2D.getLineDash() 獲得線段樣式數組.

          CanvasRenderingContext2D.setLineDash() 設置線段樣式.

          CanvasRenderingContext2D.lineDashOffset 繪制線段偏移量.

          封裝一個畫矩形的方法


          function myRect(ctxTmp, x, y, w, h) {

          ctxTmp.moveTo(x, y)

          ctxTmp.lineTo(x + w, y)

          ctxTmp.lineTo(x + w, y + h)

          ctxTmp.lineTo(x, y + h)

          ctxTmp.lineTo(x, y)

          ctxTmp.stroke()

          }


          var cas = document.querySelector('canvas')

          var ctx = cas.getContext('2d')

          myRect(ctx, 50, 50, 200, 200)

          繪制矩形

          fillRect( x , y , width , height) 填充以(x,y)為起點寬高分別為 width、height 的矩形 默認為黑色

          stokeRect( x , y , width , height) 繪制一個空心以(x,y)為起點寬高分別為 width、height 的矩形

          clearRect( x, y , width , height ) 清除以(x,y)為起點寬高分別為 width、height 的矩形 為透明

          繪制圓弧

          繪制圓弧的方法有


          CanvasRenderingContext2D.arc()

          CanvasRenderingContext2D.arcTo()

          6 個參數: x,y(圓心的坐標),半徑,起始的弧度(不是角度 deg),結束的弧度,(bool 設置方向 ! )


          var cas = document.querySelector('canvas')

          var ctx = cas.getContext('2d')


          ctx.arc(100, 100, 100, 0, degToArc(360))

          ctx.stroke()


          // 角度轉弧度

          function degToArc(num) {

          return (Math.PI / 180) * num

          }

          繪制扇形


          var cas = document.querySelector('canvas')

          var ctx = cas.getContext('2d')


          ctx.arc(300, 300, 200, degToArc(125), degToArc(300))


          // 自動連回原點

          ctx.closePath()

          ctx.stroke()


          function degToArc(num) {

          return (Math.PI / 180) * num

          }

          制作畫筆

          聲明一個變量作為標識

          鼠標按下的時候,記錄起點位置

          鼠標移動的時候,開始描繪并連線

          鼠標抬起的時候,關閉開關

          點擊查看效果圖


          var cas = document.querySelector('canvas')

          var ctx = cas.getContext('2d')


          var isDraw = false

          // 鼠標按下事件

          cas.addEventListener('mousedown', function () {

          isDraw = true

          ctx.beginPath()

          })


          // 鼠標移動事件

          cas.addEventListener('mousemove', function (e) {

          if (!isDraw) {

          // 沒有按下

          return

          }

          // 獲取相對于容器內的坐標

          var x = e.offsetX

          var y = e.offsetY

          ctx.lineTo(x, y)

          ctx.stroke()

          })


          cas.addEventListener('mouseup', function () {

          // 關閉開關了!

          isDraw = false

          })

          手動涂擦

          原理和畫布相似,只不過用的是clearRect()方法。


          點擊查看效果圖


          var cas = document.querySelector('canvas')

          var ctx = cas.getContext('2d')


          ctx.fillRect(0, 0, 600, 600)


          // 開關

          var isClear = false


          cas.addEventListener('mousedown', function () {

          isClear = true

          })


          cas.addEventListener('mousemove', function (e) {

          if (!isClear) {

          return

          }

          var x = e.offsetX

          var y = e.offsetY

          var w = 20

          var h = 20

          ctx.clearRect(x, y, w, h)

          })


          cas.addEventListener('mouseup', function () {

          isClear = false

          })

          刮刮樂

          首先需要設置獎品和畫布,將畫布置于圖片上方蓋住,

          隨機設置生成獎品。

          當手觸摸移動的時候,可以擦除部分畫布,露出獎品區。

          點擊查看效果圖


          <div>

          <img src="./images/2.jpg" alt="" />

          <canvas width="600" height="600"></canvas>

          </div>

          css


          img {

          width: 600px;

          height: 600px;

          position: absolute;

          top: 10%;

          left: 30%;

          }


          canvas {

          width: 600px;

          height: 600px;

          position: absolute;

          top: 10%;

          left: 30%;

          border: 1px solid #000;

          }

          js


          var cas = document.querySelector('canvas')

          var ctx = cas.getContext('2d')

          var img = document.querySelector('img')

          // 加一個遮罩層

          ctx.fillStyle = '#ccc'

          ctx.fillRect(0, 0, cas.width, cas.height)

          setImgUrl()

          // 開關

          var isClear = false

          cas.addEventListener('mousedown', function () {

          isClear = true

          })

          cas.addEventListener('mousemove', function (e) {

          if (!isClear) {

          return

          }

          var x = e.offsetX

          var y = e.offsetY

          ctx.clearRect(x, y, 30, 30)

          })

          cas.addEventListener('mouseup', function () {

          isClear = false

          })


          function setImgUrl() {

          var arr = ['./images/1.jpg', './images/2.jpg', './images/3.jpg', './images/4.jpg']

          // 0-3

          var random = Math.round(Math.random() * 3)

          img.src = arr[random]

          }

          更多demo,請查看 github.com/Michael-lzg…


          新版vue-router的hooks用法

          seo達人

          雖然Vue 3還沒有正式發布,但是熱愛新技術的我早已按捺不住自己的內心,開始嘗試在小項目中使用它了。


          根據這篇《今日凌晨Vue3 beta版震撼發布,竟然公開支持腳手架項目!》我搭建了一個Vue 3的腳手架項目,用這種方式搭建的腳手架項目不僅僅只有vue是新版的,就連vue-router、vuex都是的。


          給大家截一下package.json的圖:




          可以看到vue-router和vuex都已經開啟4.0時代啦!


          不過其實我并沒有去了解過vue-router 4.0的新用法什么的,因為我覺得它不像vue 3.0都已經進行到beta的版本不會有特別大的變動。


          而vue-router 4.0還是alpha的階段,所以我認為現在去學習它有些為時尚早。但卻就是它!差點釀成了一場慘劇。


          舊版vue + vue-router的使用方式

          假如你在路由里面定義了一個動態參數通常都會這么寫:


          {

             path: '/:id'

          }

          然后用編程式導航的時候通常會這樣去寫:


          this.$router.push('/123')

          在組件中是這樣獲取這個參數的:


          this.$route.params.id

          我以為的新版vue + vue-router的使用方式

          由于vue 3.0的Composition API中沒有this了,所以我想到了通過獲取組件實例的方式來獲取$route:


          import { defineComponent, getCurrentInstance } from 'vue'


          export default defineComponent((props, context) => {

             const { ctx } = getCurrentInstance()

             

             console.log(ctx.$route)

          })

          沒想到打印出來的居然是undefined!

          這是咋回事呢?

          于是我又打印了一遍ctx(ctx是當前組件上下文):




          沒有&dollar;的那些字段是我在組件中自己定義的變量,帶&dollar;的這些就是vue內置的了,找了半天發現沒有&dollar;route了,只剩下了一個&dollar;router,估計vue-router 4.0把當前路由信息都轉移到$router里面去了。


          帶著猜想,我點開了&dollar;router:




          currentRoute! 看名字的話感覺應該就是它了!于是乎我:


          import { defineComponent, getCurrentInstance } from 'vue'


          export default defineComponent((props, context) => {

             const { ctx } = getCurrentInstance()

             

             console.log(ctx.$router.currentRoute.value.params.id)

          })

          果然獲取到了!好開心!


          實際的新版vue + vue-router用法

          在接下來的過程中我用ctx.&dollar;router代替了原來的this.&dollar;router、用ctx.&dollar;router.currentRoute.value代替了原先的this.&dollar;route。


          盡管在接下來的進度中并沒有出現任何的bug,程序一直都是按照我所設想的那樣去運行的。


          但在項目打包后卻出現了意想不到的bug:在跳轉路由的時候報了一個在undefined上面沒有push的錯誤。


          奇了怪了,在開發階段程序都沒有任何的報錯怎么一打包就不行了呢?根據我多年的開發經驗,我很快就定位到了是vue-router的錯誤。


          難道這樣寫是錯的嗎?可是我打印了ctx,它里面明明有一個&dollar;router、&dollar;router里面明明就有currentRoute、currentRoute里面明明就有一個value、value里面明明就有params、params里面我一點開明明就看到了傳過來的參數啊:




          估計可能是vue-router的bug,果然alpha階段的產物不靠譜,我開始后悔使用新版的vue腳手架項目了。


          vue-router里的hooks

          不過這時我突然靈光一現,vue 3不是受到了react hooks的啟發才產生了Composition API的嗎?


          那么估計vue-router肯定也會受到react-router的啟發了!


          還好我學過react,果然技多不壓身?。」烙嬂锩婵隙ㄊ怯幸粋€useXxx,就像這樣:


          import { useXxx } from 'vue-router'

          那么應該是use什么呢?按理來說應該會盡量的和以前的API保持一定的聯系,我猜應該是useRoute和useRouter吧!


          為了驗證我的想法,我打開了node_modules找到了vue-router的源碼:




          果不其然,在第2454和第2455行我發現它導出了useRoute和useRouter,那么就是它了:


          import { defineComponent } from 'vue'

          import { useRoute, useRouter } from 'vue-router'


          export default defineComponent(_ => {

             const route = useRoute()

             const router = useRouter()


             console.log(route.params.id)

             router.push('/xxx/xxx')

          })

          使用這種方式不但可以成功跳轉路由,也同樣可以獲取到路由傳過來的參數,這次再打包試了一下,果然就沒有之前的那個報錯了。


          結語

          估計以后的vue全家桶要開啟全民hooks的時代了,在翻看源碼的同時我發現他們把一些示例都寫在了vue-router/playground文件夾下了,在里面我發現了一些有趣的用法。


          如果有時間的話我會仔細研究一下然后出一篇更加深入的文章給大家,當然如果已經有小伙伴等不及我出新文章的話可以直接進入vue-router-next的github地址:


          https://github.com/vuejs/vue-router-next

          它的示例都放在了playground這個文件夾下,期待你們研究明白后出一篇更加深入的文章!

          前端實現生成帶有樣式的excel表格 Node和瀏覽器讀寫Excel文件探究實踐

          seo達人

          最近碰到個需要自動生成表格的任務,作為前端的我,就想在 node 和瀏覽器中生成強大的表格,所以特此研究了很多關于表格的 npm 庫

          支持讀寫 Excel 的 node.js 模塊

          node-xlsx: 基于 Node.js 解析 excel 文件數據及生成 excel 文件,僅支持 xlsx 格式文件

          js-xlsx: 目前 Github 上 star 數量最多的處理 Excel 的庫,支持解析多種格式表格 XLSX / XLSM / XLSB / XLS / CSV,解析采用純 js 實現,寫入需要依賴 nodejs 或者 FileSaver.js 實現生成寫入 Excel,可以生成子表 Excel,功能強大,但上手難度稍大。不提供基礎設置 Excel 表格 api 例單元格寬度,文檔有些亂,不適合快速上手;普通版本不支持定義字體、顏色、背景色等,有這個功能需要的可以使用 pro 版,是要聯系客服收費的,害我照著 API 設置調試了好多次都失敗。好在樣式設置問題有一些教程,通過研究本人已解決,可設置寬度顏色等等,見根目錄本人修改的 xlsx.js

          xlsx-style 基于 xlsx 封裝的樣式庫,可以在 xlsx 的基礎上設置樣式。樣式不全,寬度都設置不了,好多年前作者就不維護了.寬度設置問題本人已解決了,見修改的 xlsx-style.js 文件

          exceljs 在使用此庫之前,本人已花費了很大的精力,用以上庫做好了表格,但是發現不能設置頁眉頁腳,添加圖片,打印選項設置等等,直到發現了這個庫,文檔齊全,功能強大,并且還免費.但是star較少,差一點就錯過了。本教程主要針對這個庫

          代碼庫地址

          https://github.com/lingxiaoyi/excel

          安裝

          npm install


          npm install -g nodemon


          調試使用,替代 node 命令,實現保存文件,node 自動重新啟動執行,必須全局安裝才能運行


          使用

          nodemon app.js


          js-xlsx 具體 api 使用方法請參考 main.js demo 使用,app.js 中修改為 require('./src/main.js');

          exceljs 具體 api 使用方法請參考 main-exceljs.js demo 使用,app.js 中修改為 require('./src/main-exceljs.js');

          因為每次生成完表格,每次都需要打開表格查看樣式,在 windows 電腦中,打開表格之后就鎖定不能生成新文件了,本來想著能導出一個 html 文件對應表格的樣式


          node 調試

          vscode 中打開調試右側設置編輯,將下方代碼復制進去,點 nodemon 啟動就可以進行 debug 調試了


          {

               "type": "node",

               "request": "launch",

               "name": "nodemon",

               "runtimeExecutable": "nodemon",

               "program": "${workspaceFolder}/app.js",

               "restart": true,

               "console": "integratedTerminal",

               "internalConsoleOptions": "neverOpen",

               "skipFiles": ["<node_internals>/**"]

             },

          webpack 目錄的作用

          每次生成完新表格,都需要重新打開表格查看樣式,在 windows 電腦中,打開表格之后就鎖定了,再次生成新表格就會報錯,文件已鎖定,不能寫入,對于想偷懶的我,能不能實現像 webpack 熱更新功能那種,修改樣式 js 頁面自動更新呢?


          wps 自帶另存 html 文件功能,但是沒有提供生成的 api ,網上也搜索不到對應的轉換功能,

          本來以為自己要實現一套表格轉 html 的功能。通過不斷嘗試,偶然間發現手機瀏覽器可以直接打開預覽 xlsx 文件,內心狂喜啊


          使用方法

          進入 webpack 目錄安裝依賴包,安裝好之后執行


          npm run dev


          啟動成功之后,會自動打開帶有 ip 地址的預覽地址,此時在電腦瀏覽器會自動下載 xlsx 文件,忽略不管,用手機直接打開此地址,就能看到 xlsx 表格的內容了,并且每次新修改內容和樣式,都會自動刷新頁面顯示新表格.


          小技巧

          谷歌瀏覽器插件:


          生成二維碼的插件生成二維碼方便手機掃描

          劃詞翻譯 用來翻譯一些看不懂的英文文檔

          browser 目錄

          瀏覽器中實現生成 xlsx 表格方法


          進入 browser 目錄安裝依賴包,安裝好之后執行


          npm run dev


          啟動成功之后,拖動根目錄 src 下的李四表格到頁面上的輸入框里,成功生成表格之后會生成一個下載鏈接地址,右鍵在新標簽頁打開鏈接,即會生成一個新的表格文件出來,完整 api 使用和 demo 文件請參考 index.js


          vue 和 react 用法可以參考此例子,如果有必要也可以此版本庫的例子


          一些概念

          在使用這個庫之前,先介紹庫中的一些概念。


          workbook 對象,指的是整份 Excel 文檔。我們在使用 js-xlsx 讀取 Excel 文檔之后就會獲得 workbook 對象。

          worksheet 對象,指的是 Excel 文檔中的表。我們知道一份 Excel 文檔中可以包含很多張表,而每張表對應的就是 worksheet 對象。

          cell 對象,指的就是 worksheet 中的單元格,一個單元格就是一個 cell 對象。

          xlsx 使用注意事項

          constXLSX = require('xlsx');

          let html = XLSX.utils.sheet_to_html(workbook.Sheets.Sheet1)

          生成 html 的用法,并且不會有任何樣式


          exceljs 使用注意

          讀取文件問題

          因為 exceljs 讀取文件不支持 sync 同步讀取,給的實例也是 await 例子.導致我讀取完遇到一個問題,就是老是生成不成功,最后發現必須要把所有邏輯全部放入函數中,像下方這樣


          (async function (params) {

           let res = await workbook.xlsx.readFile(`${__dirname}/趙六.xlsx`);

           //執行所有數據處理邏輯

           //執行寫的邏輯

           workbook.xlsx.writeFile(path.resolve(__dirname, '../webpack/test222.xlsx'));

          });

          所有邏輯全部要寫入這個函數中,這樣本來是可以的,但是出錯調試幾率較大,并且讀取到的數據龐大還需要額外處理,所以我讀取數據邏輯就用的 node-xlsx,十分簡單方便,如果你用的 exceljs 讀取文件數據出現問題,大概率是異步同步邏輯搞錯了,多加注意即可

          寬度設置

          列寬不知道是以什么為單位,反正不是像素(已測量),例子中是以厘米為單位再乘以 4.7 的結果設置的,4.7 是不斷測試的結果.

          快捷查看列寬的方法,打開 wps 表格,長按列與列字母間的豎線,就能看到列寬,取厘米的單位即可.見下圖




          前景色

          前景色設置必須右鍵單元格選擇設置單元格格式,然后選擇圖案樣式選擇顏色,就可以前景色填充

          worksheet.getCell('A2').fill = { type: 'pattern', pattern:'darkTrellis', fgColor:{argb:'FFFFFF00'}, bgColor:{argb:'FF0000FF'} };


          背景色

          worksheet.getCell('A2').fill = { type: "pattern", pattern: "solid", fgColor: { argb: next.bgColor }, }


          排版不一致的問題

          解決 Mac 下編輯 Microsoft Office Word 文檔與 Windows 排版不一致的問題,,不同的系統用 wps 打開相同的表格,打印預覽的時候,表格寬度顯示不一樣

          問題詳細說明地址


          我的解決辦法就是 mac 下顯示正常,按 mac 下的寬度來設置就可以了


          參考資料

          exceljs

          node-xlsx

          js-xlsx

          函數節流與函數防抖的區別

          seo達人

          函數節流與函數防抖是我們解決頻繁觸發DOM事件的兩種常用解決方案,但是經常傻傻分不清楚。。。這不,在項目中又用遇到了,在此處記錄一下



          函數防抖 debounce

          原理:將若干函數調用合成為一次,并在給定時間過去之后,或者連續事件完全觸發完成之后,調用一次(僅僅只會調用一次?。。。。。。。。。?。



          舉個栗子:滾動scroll事件,不?;瑒訚L輪會連續觸發多次滾動事件,從而調用綁定的回調函數,我們希望當我們停止滾動的時,才觸發一次回調,這時可以使用函數防抖。



          原理性代碼及測試:



          // 給盒子較大的height,容易看到效果

          <style>

              * {

                  padding: 0;

                  margin: 0;

              }



              .box {

                  width: 800px;

                  height: 1200px;

              }

          </style>

          <body>

              <div class="container">

                  <div class="box" style="background: tomato"></div>

                  <div class="box" style="background: skyblue"></div>

                  <div class="box" style="background: red"></div>

                  <div class="box" style="background: yellow"></div>

              </div>

              <script>

                  window.onload = function() {

                      const decounce = function(fn, delay) {

                          let timer = null



                          return function() {

                              const context = this

                              let args = arguments

                              clearTimeout(timer) // 每次調用debounce函數都會將前一次的timer清空,確保只執行一次

                              timer = setTimeout(() => {

                                  fn.apply(context, args)

                              }, delay)

                          }

                      }



                      let num = 0



                      function scrollTap() {

                          num++

                          console.log(看看num吧 ${num})

                      }

                      // 此處的觸發時間間隔設置的很小

                      document.addEventListener('scroll', decounce(scrollTap, 500))

                      // document.addEventListener('scroll', scrollTap)

                  }

              </script>

          </body>



          此處的觸發時間間隔設置的很小,如果勻速不間斷的滾動,不斷觸發scroll事件,如果不用debounce處理,可以發現num改變了很多次,用了debounce函數防抖,num在一次上時間的滾動中只改變了一次。



          調用debouce使scrollTap防抖之后的結果:



          直接調用scrollTap的結果:





          補充:瀏覽器在處理setTimeout和setInterval時,有最小時間間隔。

          setTimeout的最短時間間隔是4毫秒;

          setInterval的最短間隔時間是10毫秒,也就是說,小于10毫秒的時間間隔會被調整到10毫秒。

          事實上,未優化時,scroll事件頻繁觸發的時間間隔也是這個最小時間間隔。

          也就是說,當我們在debounce函數中的間隔事件設置不恰當(小于這個最小時間間隔),會使debounce無效。



          函數節流 throttle

          原理:當達到了一定的時間間隔就會執行一次;可以理解為是縮減執行頻率



          舉個栗子:還是以scroll滾動事件來說吧,滾動事件是及其消耗瀏覽器性能的,不停觸發。以我在項目中碰到的問題,移動端通過scroll實現分頁,不斷滾動,我們不希望不斷發送請求,只有當達到某個條件,比如,距離手機窗口底部150px才發送一個請求,接下來就是展示新頁面的請求,不停滾動,如此反復;這個時候就得用到函數節流。



          原理性代碼及實現



          // 函數節流 throttle

          // 方法一:定時器實現

          const throttle = function(fn,delay) {

            let timer = null



            return function() {

              const context = this

              let args = arguments

              if(!timer) {

                timer = setTimeout(() => {

                  fn.apply(context,args) 

                  clearTimeout(timer) 

                },delay)

              }

            }

          }



          // 方法二:時間戳

          const throttle2 = function(fn, delay) {

            let preTime = Date.now()



            return function() {

                const context = this

                let args = arguments

                let doTime = Date.now()

                if (doTime - preTime >= delay) {

                    fn.apply(context, args)

                    preTime = Date.now()

                }

            }

          }



          需要注意的是定時器方法實現throttle方法和debounce方法的不同:



          在debounce中:在執行setTimeout函數之前總會將timer用setTimeout清除,取消延遲代碼塊,確保只執行一次

          在throttle中:只要timer存在就會執行setTimeout,在setTimeout內部每次清空這個timer,但是延遲代碼塊已經執行啦,確保一定頻率執行一次




          我們依舊可以在html頁面中進行測試scroll事件,html和css代碼同debounce,此處不贅述,運行結果是(可以說是一場漫長的滾輪滾動了):





          最后再來瞅瞅項目中封裝好的debounce和throttle函數,可以說是很優秀了,考慮的特別全面,希望自己以后封裝的函數也能考慮的這么全面吧,加油!



          /*

           
          空閑控制 返回函數連續調用時,空閑時間必須大于或等于 wait,func 才會執行

           

           
          @param  {function} func        傳入函數,最后一個參數是額外增加的this對象,.apply(this, args) 這種方式,this無法傳遞進函數

            @param  {number}   wait        表示時間窗口的間隔

           
          @param  {boolean}  immediate   設置為ture時,調用觸發于開始邊界而不是結束邊界

            @return {function}             返回客戶調用函數

           
          /

          const debounce = function(func, wait, immediate) {

              let timeout, args, context, timestamp, result;



              const later = function() {

                  // 據上一次觸發時間間隔

                  let last = Number(new Date()) - timestamp;



                  // 上次被包裝函數被調用時間間隔last小于設定時間間隔wait

                  if (last < wait && last > 0) {

                      timeout = setTimeout(later, wait - last);

                  } else {

                      timeout = null;

                      // 如果設定為immediate===true,因為開始邊界已經調用過了此處無需調用

                      if (!immediate) {

                          result = func.call(context, ...args, context);

                          if (!timeout) {

                              context = args = null;

                          }

                      }

                  }

              };



              return function(..._args) {

                  context = this;

                  args = _args;

                  timestamp = Number(new Date());

                  const callNow = immediate && !timeout;

                  // 如果延時不存在,重新設定延時

                  if (!timeout) {

                      timeout = setTimeout(later, wait);

                  }

                  if (callNow) {

                      result = func.call(context, ...args, context);

                      context = args = null;

                  }



                  return result;

              };

          };



          /*

           
          頻率控制 返回函數連續調用時,func 執行頻率限定為 次 / wait

           

           
          @param  {function}   func      傳入函數

            @param  {number}     wait      表示時間窗口的間隔

           
          @param  {object}     options   如果想忽略開始邊界上的調用,傳入{leading: false}。

                                           如果想忽略結尾邊界上的調用,傳入{trailing: false}

           
          @return {function}             返回客戶調用函數

           */

          const throttle = function(func, wait, options) {

              let context, args, result;

              let timeout = null;

              // 上次執行時間點

              let previous = 0;

              if (!options) options = {};

              // 延遲執行函數

              let later = function() {

                  // 若設定了開始邊界不執行選項,上次執行時間始終為0

                  previous = options.leading === false ? 0 : Number(new Date());

                  timeout = null;

                  result = func.apply(context, args);

                  if (!timeout) context = args = null;

              };

              return function(..._args) {

                  let now = Number(new Date());

                  // 首次執行時,如果設定了開始邊界不執行選項,將上次執行時間設定為當前時間。

                  if (!previous && options.leading === false) previous = now;

                  // 延遲執行時間間隔

                  let remaining = wait - (now - previous);

                  context = this;

                  args = _args;

                  // 延遲時間間隔remaining小于等于0,表示上次執行至此所間隔時間已經超過一個時間窗口

                  // remaining大于時間窗口wait,表示客戶端系統時間被調整過

                  if (remaining <= 0 || remaining > wait) {

                      clearTimeout(timeout);

                      timeout = null;

                      previous = now;

                      result = func.apply(context, args);

                      if (!timeout) context = args = null;

                      //如果延遲執行不存在,且沒有設定結尾邊界不執行選項

                  } else if (!timeout && options.trailing !== false) {

                      timeout = setTimeout(later, remaining);

                  }

                  return result;

              };

          };


          超簡單入門Vuex小示例

          seo達人

          寫在前面

          本文旨在通過一個簡單的例子,練習vuex的幾個常用方法,使初學者以最快的速度跑起來一個vue + vuex的示例。

          學習vuex需要你知道vue的一些基礎知識和用法。相信點開本文的同學都具備這個基礎。

          另外對vuex已經比較熟悉的大佬可以忽略本文。

          生成基于vue的項目

          基于vue-cli腳手架生成一個vue項目
          常用npm命令:

          npm i vue-vli -g vue --version vue init webpack 項目名 

          進入項目目錄,使用npm run dev先試著跑一下。

          一般不會出現問題,試跑成功后,就可以寫我們的vuex程序了。

          使用vue完成的示例

          使用vuex首先得安裝vuex,命令:

          npm i vuex --save

          介紹一下我們的超簡單Demo,一個父組件,一個子組件,父組件有一個數據,子組件有一個數據,想要將這兩個數據都放置到vuex的state中,然后父組件可以修改自己的和子組件的數據。子組件可以修改父組件和自己的數據。

          先放效果圖,初始化效果如下:

          如果想通過父組件觸發子組件的數據,就點“改變子組件文本”按鈕,點擊后效果如下:

          如果想通過子組件修改父組件的數據,就在子組件點擊“修改父組件文本”按鈕,點擊后效果如下:

          代碼文件介紹

          首先是Parent.vue組件

          <template> <div class="parent"> <h3>這里是父組件</h3> <button type="button" @click="clickHandler">修改自己文本</button> <button type="button" @click="clickHandler2">修改子組件文本</button> <div>Test: {{msg}}</div> <child></child> </div> </template> <script> import store from '../vuex' import Child from './Child.vue' export default { computed: {
                      msg(){ return store.state.testMsg;
                      }
                  }, methods:{
                      clickHandler(){
                          store.commit('changeTestMsg', '父組件修改自己后的文本')
                      },
                      clickHandler2(){
                          store.commit('changeChildText', '父組件修改子組件后的文本')
                      }
                  }, components:{ 'child': Child
                  },
                  store,
              } </script> <style scoped> .parent{ background-color: #00BBFF; height: 400px;
              } </style> 

          下面是Child.vue子組件

          <template> <div class="child"> <h3>這里是子組件</h3> <div>childText: {{msg}}</div> <button type="button" @click="clickHandler">修改父組件文本</button> <button type="button" @click="clickHandler2">修改自己文本</button> </div> </template> <script> import store from '../vuex' export default { name: "Child", computed:{
                      msg(){ return store.state.childText;
                      }
                  }, methods: {
                      clickHandler(){
                          store.commit("changeTestMsg", "子組件修改父組件后的文本");
                      },
                      clickHandler2(){
                          store.commit("changeChildText", "子組件修改自己后的文本");
                      }
                  },
                  store
              } </script> <style scoped> .child{ background-color: palegreen; border:1px solid black; height:200px; margin:10px;
              } </style> 

          最后是vuex的配置文件

           import Vue from 'vue' import Vuex from 'vuex';
          
          Vue.use(Vuex) const state = { testMsg: '原始文本', childText:"子組件原始文本" } const mutations = {
              changeTestMsg(state, str){
                  state.testMsg = str;
              },
              changeChildText(state, str){
                  state.childText = str;
              }
          
          } const store = new Vuex.Store({ state: state, mutations: mutations
          }) export default store;

          后記

          通過該vuex示例,了解vuex的常用配置及方法調用。希望對不怎么熟悉vuex的同學快速上手vuex項目有點幫助。

          因為沒太多東西,我自己也是剛接觸,本例就不往GitHub扔了,如果嘗試了本例,但是沒有跑起來的同學,可以一起交流下。

          移動端列表查詢最佳實踐

          seo達人

          無論是 pc 端還是移動端,無可避免都會涉及到列表查詢有關的操作,但對于這兩種不同的設備,其列表查詢的最佳處理方式也是完全不同。

          對于 pc 端列表查詢來說,前端通常是給與服務端當前需要獲取的數據量(如 pageCount,limit 等參數)以及所需要獲取數據的位置(如 pageSize,offset 等參數)作為查詢條件。然后服務端然后返回數據總數,以及當前數據,前端再結合這些數據顯示頁面總數等信息。這里我稱為相對位置取數。

          對于移動端而言,沒有pc 端那么大的空間展示以及操作,所以基本上都會采用下拉取數這種方案。

          那么我們在處理移動端列表查詢時候使用這種相對位置取數會有什么問題呢?

          相對位置取數存在的問題

          性能劣勢

          通過相對位置取數會具有性能問題,因為一旦使用 offset 信息來獲取數據,隨著頁數的增加,響應速度也會變的越來越慢。因為在數據庫層面,我們每次所獲取的數據都是“從頭開始第幾條”,每次我們都需要從第一條開始計算,計算后舍棄前面的數據,只取最后多條數據返回前端。

          當然了,對于相對位置取數來說,數據庫優化是必然的,這里我就不多做贅述了。對于前端開發來說,優秀的的查詢條件設計可以在一定方面解決此問題。

          數據顯示重復

          事實上,對于一個實際運行的項目而言,數據更新才是常態,如果數據更新的頻率很高或者你在當前頁停留的時間過久的話,會導致當前獲取的數據出現一定的偏差。

          例如:當你在獲取最開始的 20 條數據后,正準備獲取緊接著的后 20 條數據時,在這段時間內 ,發生了數據增加,此時移動端列表就可能會出現重復數據。雖然這個問題在 pc 端也存在,但是 pc 端只會展示當前頁的信息,這樣就避免了該問題所帶來的負面影響。

          結合列表 key 維持渲染正確

          我們在上面的問題中說明了,移動端下拉加載中使用相對位置查詢取數是有問題的。

          那么,如果當前不能迅速結合前后端進行修改 api 的情況下,當服務端傳遞過來的數據與用戶想要得的數據不一致,我們必須在前端進行處理,至少處理數據重復問題所帶來的負面影響。

          因為當前分頁請求時無狀態的。在分頁取到數據之后前端可以對取得的數據進行過濾,過濾掉當前頁面已經存在的 key(例如 id 等能夠確定的唯一鍵)。

          通過這種處理方式,我們至少可以保證當前用戶看到的數據不會出現重復。同時當列表數據可以編輯修改的時候,也不會出現因為 key 值相同而導致數據錯亂。

          通過絕對位置獲取數據

          如果不使用相對位置獲取數據,前端可以利用當前列表中的最后一條數據作為請求源參數。前端事先記錄最后一條數據的信息。例如當前的排序條件為創建時間,那么記錄最后一條數據的創建時間為主查詢條件(如果列表對應的數據不屬于個人,可能創建時間不能唯一決定當前數據位置,同時還需要添加 ID 等信息作為次要查詢條件)。

          當我們使用絕對位置獲取數據時候,雖然我們無法提供類似于從第 1 頁直接跳轉 100 頁的查詢請求,但對于下拉加載這種類型的請求,我們不必擔心性能以及數據重復顯示的問題。

          對于相對位置取數來說,前端可以根據返回數據的總數來判斷。但當使用絕對位置取數時,即使獲取數據總數,也無法判斷當前查詢是否存在后續數據。

          從服務器端實現的角度來說,當用戶想要得到 20 條數據時候,服務端如果僅僅只向數據庫請求 20 條數據,是無法得知是否有后續數據的。服務端可以嘗試獲取當前請求的數據條數 + 1, 如向數據庫請求 21 條數據,如果成功獲得 21 條數據,則說明至少存在著 1 條后續數據,這時候,我們就可以返回 20 條數據以及具有后續數據的信息。但如果我們請求 21 條數據卻僅僅只能獲取 20 條數據(及以下),則說明沒有后續數據。

          如可以通過 “hasMore” 字段來表示是否能夠繼續下拉加載的信息。

          { data: [], hasMore: true }

          結合 HATEOAS 設計優化

          事實上,前面我們已經解決了移動端處理列表查詢的問題。但是我們做的還不夠好,前端還需要結合排序條件來處理并提供請求參數,這個操作對于前端來說也是一種負擔。那么我們就聊一下 HATEOAS 。

          HATEOAS (Hypermedia As The Engine Of Application State, 超媒體即應用狀態引起) 這個概念最早出現在 Roy Fielding 的論文中。REST 設計級別如下所示:

          • REST LEVEL 0: 使用 HTTP 作為傳輸方式
          • REST LEVEL 1: 引入資源的概念(每一個資源都有對應的標識符和表達)
          • REST LEVEL 2: 引入 HTTP 動詞(GET 獲取資源/POST 創建資源/PUT 更新或者創建字樣/DELETE 刪除資源 等)
          • REST LEVEL 3: 引入 HATEOAS (在資源的表達中包含了鏈接信息??蛻舳丝梢愿鶕溄觼戆l現可以執行的動作)

          HATEOAS 會在 API 返回的數據中添加下一步要執行的行為,要獲取的數據等 URI 的鏈接信息??蛻舳酥灰@取這些信息以及行為鏈接,就可以根據這些信息進行接下來的操作。

          對于當前的請求來說,服務端可以直接返回下一頁的信息,如

          { data: [], hasMore: true, nextPageParams: {}    
          }

          服務端如此傳遞數據,前端就不需要對其進行多余的請求處理,如果當前沒有修改之前的查詢以及排序條件,則只需要直接返回 “nextPageParams” 作為下一頁的查詢條件即可。

          這樣做的好處不但符合 REST LEVEL 3,同時也減輕了前端的心智模型。前端無需配置下一頁請求參數。只需要在最開始查詢的時候提供查詢條件即可。

          當然,如果前端已經實現了所有排序添加以及查詢條件由服務端提供,前端僅僅提供組件,那么該方案更能體現優勢。 前端是不需要知道當前業務究竟需要什么查詢條件,自然也不需要根據查詢條件來組織下一頁的條件。同時,該方案的輸入和輸出都由后端提供,當涉及到業務替換( 查詢條件,排序條件修改)時候,前端無需任何修改便可以直接替換和使用。

          其他注意事項

          一旦涉及到移動端請求,不可避免的會有網絡問題,當用戶在火車或者偏遠地區時候,一旦下拉就會涉及取數,但是當前數據沒有返回之前,用戶多次下拉可能會有多次取數請求,雖然前端可以結合 key 使得渲染不出錯,但是還是會在緩慢的網絡下請求多次,無疑雪上加霜。這時候我們需要增加條件變量 loading。

          偽代碼如下所示:

          // 查詢 function search(cond) {
            loading = true api.then(res => {
                loading = false }).catch(err => {
                loading = false })
          } // 獲取下一頁數據 function queryNextPage() { if (!nextPageParams) return if (!loading) return search(nextPageParams)
          }

          規范git commit的提交記錄

          seo達人

          隨著項目體積的增加,參與到項目中的同學越來越多,每個人都有自己的打 git log 的習慣:

          • 格式 1: add: 添加...
          • 格式 2: [add]: 添加...
          • 格式 3: Add 添加...

          為了形成統一的規范,達成共識,從而降低協作開發成本,需要對 git commit 記錄進行規范。

          規范 git commit 記錄

          規范 git commit 記錄,需要做兩件事情:

          • 通過交互式命令行,自動生成符合指定規范的 commit 記錄
          • 提交記錄后,在 git hooks 中進行 commit 記錄格式檢查
          問:既然已經交互式生成了規范記錄,為什么需要在 hooks 進行檢查?

          交互式生成 commit 記錄,需要用戶調用自定義的 npm scripts,例如npm run commit。但還是可以直接調用原生 git 命令 git commit 來提交記錄。而檢查是在正式提交前進行的,因此不符合要求的記錄不會生效,需要重新 commit。

          調研:交互式 commit log 規范方案

          前期調研結果,關于 commit 提示有兩種做法:

          1. 直接使用 commitizen 中常用的 adapter
          2. 根據團隊的需要,自定義 adapter

          方法 1 的優缺點:

          優點 1: 直接安裝對應的 adapter 即可

          優點 2: 無開發成本

          缺點 1: 無法定制,不一定滿足團隊需要

          方法 2 的優缺點:

          優點 1: 可定制,滿足開發需求

          優點 2: 單獨成庫,發布 tnpm,作為技術建設

          缺點 1: 需要單獨一個倉庫(但開發成本不高)

          代碼實現

          在實際工作中,發現方法 1 中的常用規范,足夠覆蓋團隊日常開發場景。所以,選擇了方法 1.

          step1: 安裝 npm 包

          npm i --save-dev commitizen cz-conventional-changelog @commitlint/cli @commitlint/config-conventional husky

          添加 package.json 的配置:

          "scripts": { "commit": "git-cz" }, "husky": { "hooks": { "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" }
          }, "config": { "commitizen": { "path": "./node_modules/cz-conventional-changelog" }
          }

          在項目根目錄下創建commitlint.config.js

          module.exports = { extends: ["@commitlint/config-conventional"]
          };

          使用方法:不再使用git commit -m ...,而是調用npm run commit。

          <img src="https://tva1.sinaimg.cn/large/006tNbRwly1gbjcfr3xb5j30cw00tjrd.jpg" style="width: 100% !important;"/>

          使用scss開發小程序(各種小程序平臺通用)

          seo達人

          微信小程序的wxss、阿里旗下淘寶、支付寶小程序的acss等等語法很類似原生css,但是在web開發里用慣了動態css語言,再寫回原生css很不習慣,尤其是父子樣式的嵌套寫法非常繁瑣。

          因此,我希望能有一個自動化構建方案,能夠簡單地將scss轉換成小程序的樣式語言。

          方案1

          以前寫微信小程序的依賴庫時用過,使用gulp編譯,將源碼和編譯后的代碼分別放到src和dist兩個目錄。gulp會處理src下面的所有文件,將其中的scss轉換成css,并將其他所有文件原封不動挪到dist下相應位置。

          這里就不詳細說了,代碼參考Wux

          方案2

          非常簡單直接,使用Webstorm/IDEAFile Watchers功能實時轉換。

          安裝Ruby和sass

          確保命令行輸入sass -v能出現版本號,安裝過程略。

          安裝File Watchers

          到插件市場上搜索并安裝(已安裝則跳過)

          1.png

          添加scss的轉換腳本

          現在安裝完插件打開項目會自動彈出scss轉css的向導,方便了很多。但還需要做一些修改,配置如下:

          2.png

          首先要將生成文件的后綴名改掉,比如這里我的淘寶小程序就得是acss。

          其次,將Arguments改為:

          $FileName$:$FileNameWithoutExtension$.acss --no-cache --sourcemap=none --default-encoding utf-8 --style expanded

          如果不加--no-cache,scss文件同目錄下會出現一個.sass-cache目錄。

          如果不加--sourcemap=none, scss文件同目錄下會出現一個.map文件。

          如果不加--default-encoding utf-8, scss文件如果有中文注釋轉換就會報錯。

          style可不加,這里用的是無縮進和壓縮的風格,反正小程序打包發布時還會壓,這里保持可讀性。

          現在這個scss轉換是單獨作用于項目的,如果新建一個小程序項目,就需要重新添加(不建議設置成global,容易誤傷)。

          注意到File Watchers列表的右側操作欄下方有導入導出按鈕,可以將現在配好的設置導出保存,將來新建項目時只要導入一下就行了。


          之后還有一個問題,如果我手動將編譯后的css(即wxss或者acss,下略)文件刪除,scss文件不改動的話,就不會重新編譯出css文件。
          或者萬一監聽失效或者不夠及時,css還有可能是舊的。
          所以還需要一個命令,用來將整個目錄下的scss文件統一轉換,確保沒有遺漏和保持代碼。

          不過我看了半天sasssass-convert的文檔,沒有找到一個可用的寫法,能讓命令行遍歷指定目錄下的所有scss文件,將其轉換成css放到源文件所在目錄,并且將后綴名改為wxss或者acss。

          所以遍歷這個行為只能交給nodejs來實現,代碼如下:

          創建編譯腳本build/scss-convert.js

          var path = require("path") var fs = require("fs") const { exec } = require('child_process') const basePath = path.resolve(__dirname, '../') function mapDir(dir, callback, finish) {
            fs.readdir(dir, function(err, files) { if (err) { console.error(err) return }
              files.forEach((filename, index) => { let pathname = path.join(dir, filename)
                fs.stat(pathname, (err, stats) => { // 讀取文件信息 if (err) { console.log('獲取文件stats失敗') return } if (stats.isDirectory()) {
                    mapDir(pathname, callback, finish)
                  } else if (stats.isFile()) { if (!['.scss'].includes(path.extname(pathname))) { return }
                    callback(pathname)
                  }
                }) if (index === files.length - 1) {
                  finish && finish()
                }
              })
            })
          }
          
          mapDir(
            basePath, function (file) { const newFileWithoutExt = path.basename(file, '.scss') if (newFileWithoutExt.startsWith('_')) { return // 按照scss規則,下劃線開頭的文件不會生成css } // exec可以讓nodejs執行外部命令 exec(`sass --no-cache --sourcemap=none --default-encoding utf-8 --style expanded ${file}:${newFileWithoutExt}.acss`, { cwd: path.dirname(file) // 不寫這個會導致生成的文件出現在根目錄 }, (err, stdout, stderr) => { if (err) { console.log(err) return } console.log(`stdout: ${stdout}`)
              })
            }, function() { // console.log('xxx文件目錄遍歷完了') }
          )

          package.json里添加一條script:

           "scripts": { "scss": "node build/scss-convert",
            },

          web下的性能優化1(網絡方向)

          seo達人

          性能優化(網絡方向)

          web應用無非是兩臺主機之間互相傳輸數據包的一個過程; 如何減少傳輸過程的耗時就是網絡方向優化的重點, 優化出發點從第一篇文章中說起


          DNS解析過程的優化

          當瀏覽器從第三方服務跨域請求資源的時候,在瀏覽器發起請求之前,這個第三方的跨域域名需要被解析為一個IP地址,這個過程就是DNS解析;

          DNS緩存可以用來減少這個過程的耗時,DNS解析可能會增加請求的延遲,對于那些需要請求許多第三方的資源的網站而言,DNS解析的耗時延遲可能會大大降低網頁加載性能。


          dns-prefetch

          當站點引用跨域域上的資源時,都應在<head>元素中放置dns-prefetch提示,但是要記住一些注意事項。首先,dns-prefetch僅對跨域域上的DNS查找有效,因此請避免將其用于您當前訪問的站點


          <link rel="dns-prefetch" >

          preconnect

          由于dns-prefetch僅執行DNS查找,但preconnect會建立與服務器的連接。如果站點是通過HTTPS服務的,則此過程包括DNS解析,建立TCP連接以及執行TLS握手。將兩者結合起來可提供機會,進一步減少跨源請求的感知延遲


          <!-- 注意順序, precontent和dns-prefetch的兼容性 -->

          <link rel="preconnect" crossorigin>

          <link rel="dns-prefetch" >

          TCP傳輸階段優化

          這個前端方面好像能做的有限, 我們都知道 http協議 是基于 tcp的;

          升級http協議版本可以考慮下, 比如把 http/1.0 -> http/1.1 -> http/2;

          這個需要我們在應用服務器上配置(nginx, Apache等), 不做概述了, 另外還需要客戶端和服務器都支持哦, 目前還沒開發出穩定版本,好多只支持https,不過也不遠了...


          http2 的優勢

          #  1.多路復用: 同一個tcp連接傳輸多個資源

          這樣可以突破統一域名下只允許有限個tcp同時連接,

          這樣http1.1所做的減少請求數優化就沒有太大必要了

          如多張小圖合成一張大圖(雪碧圖),合并js和css文件


          # 2.報文頭壓縮和二進制編碼: 減少傳輸體積

          http1 中第一次請求有完整的http報文頭部,第二次請求的也是;

          http2 中第一次請求有完整的http報文頭部,第二次請求只會攜帶 path 字段;

          這樣就大大減少了發送的量。這個的實現要求客戶端和服務同時維護一個報文頭表。


          # 3.Server Push

          http2可以讓服務先把其它很可能客戶端會請求的資源(比如圖片)先push發給你,

          不用等到請求的時候再發送,這樣可以提高頁面整體的加載速度

          但目前支持性不太好...emm...

          總的來說, 在 c 端業務下不會太普及, 畢竟需要軟件支持才行...


          http 請求響應階段優化

          為了讓數據包傳輸的更快, 我們可以從兩個方面入手: 請求的數據包大小(服務器), 請求數據包的頻率(客戶端)


          減少請求文件的大小

          請求文件對應的是我們項目完成后,打包所指的靜態資源文件(會被部署到服務器), 文件越小, 傳輸的數據包也會相對較小, 講道理也會更快到達客戶端


          how to reduce a package size?

          目前我們都會使用打包工具了(比如webpack, rollup, glup 等), 如何使用工具來減小包的體積呢? 這邊建議您去官網文檔呢...當然這里列舉一下常用的手段(webpack 的), 但是注意要插件版本更新哦


          JS文件壓縮

          const UglifyJsPlugin = require('uglifyjs-webpack-plugin');

          module.exports = {

          plugins: [

            new UglifyJsPlugin({

              // 允許并發

              parallel: true,

              // 開啟緩存

              cache: true,

              compress: {

                // 刪除所有的console語句    

                drop_console: true,

                // 把使用多次的靜態值自動定義為變量

                reduce_vars: true,

              },

              output: {

                // 不保留注釋

                comment: false,

                // 使輸出的代碼盡可能緊湊

                beautify: false

              }

            })

          ]

          }

          CSS 文件壓縮

          // optimize-css-assets-webpack-plugin

          plugins: [

           new OptimizeCSSAssetsPlugin({

             assetNameRegExp: /\.css$/g,

             cssProcessor: require('cssnano'),

           }),

          ];

          html 文件壓縮

          // html-webpack-plugin

          plugins: [

           new HtmlWebpackPlugin({

             template: path.join(__dirname, 'src/index.html'),

             filename: 'index.html',

             chunks: ['index'],

             inject: true,

             minify: {

               html5: true,

               collapseWhitespace: true,

               preserveLineBreaks: false,

               minifyCSS: true,

               minifyJS: true,

               removeComments: false,

             },

           }),

          ];

          source map 文件關閉

          tree shaking

          1.代碼不會被執行,不可到達,比如 if(false){// 這里邊的代碼}

          2.代碼執行的結果不會被用到

          3.代碼只會影響死變量(只寫不讀)

          4.方法不能有副作用


          // 原理相關: 以后在研究

          利用 ES6 模塊的特點:

           只能作為模塊頂層的語句出現

           import 的模塊名只能是字符串常量

           import binding 是 immutable 的

          代碼擦除: uglify 階段刪除無用代碼

          scope hoisting(作用域提升)

          分析出模塊之間的依賴關系,盡可能的把打散的模塊合并到一個函數中去,但前提是不能造成代碼冗余


          const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');

          module.exports = {

           resolve: {

             // 針對 Npm 中的第三方模塊優先采用 jsnext:main 中指向的 ES6 模塊化語法的文件

             mainFields: ['jsnext:main', 'browser', 'main']

           },

           plugins: [

             // 開啟 Scope Hoisting

             new ModuleConcatenationPlugin(),

           ],

          };

          項目中使用按需加載,懶加載(路由,組件級)

          const router = new VueRouter({

           routes: [

             { path: '/foo', component: () => import(/* webpackChunkName: "foo" */ './Foo.vue') }

             { path: '/bar', component: () => import(/* webpackChunkName: "bar" */ './Bar.vue') }

           ]

          })

          開啟 gizp 壓縮

          有時候啟用也會消耗服務器性能, 看情況使用吧

          暫時先提這么些吧...后續想到了再加


          減少請求頻率

          因為同一域名下 tcp 連接數的限制導致過多的請求會排隊阻塞, 所以我們需要盡量控制請求的數量和頻率


          常見措施

          將靜態資源的內聯到HTML中

          這樣這些資源無需從服務器獲取, 但可能影響到渲染進程...


          <!-- 1.小圖片內聯 base64 (url-loader) -->

          <!-- 2.css內聯 -->

          <!-- 3.js內聯 -->

          <script>

           ${require('raw-loader!babel-loader!./node_modules/lib-flexible/flexible.js')}

          </script>

          利用各級緩存(下一篇存儲方面介紹)

          通常都是在服務端做相關配置, 但你要知道


          我們可以利用http緩存(瀏覽器端)來減少和攔截二次請求, 當然一般都是在服務端設置的;

          服務器端也可以設置緩存(redis等), 減少數據查詢的時間同樣可以縮短整個請求時間

          利用本地存儲

          我們可以將常用不變的信息存在本地(cookie,storage API 等);

          判斷存在就不去請求相關的接口, 或者定期去請求也是可以的

          花錢買 CDN 加速

          CDN 又叫內容分發網絡,通過把資源部署到世界各地,用戶在訪問時按照就近原則從離用戶最近的服務器獲取資源,從而加速資源的獲取速度。 CDN 其實是通過優化物理鏈路層傳輸過程中的網速有限、丟包等問題來提升網速的...


          購買 cdn 服務器;

          然后把網頁的靜態資源上傳到 CDN 服務上去,

          在請求這些靜態資源的時候需要通過 CDN 服務提供的 URL 地址去訪問;


          # 注意, cdn 緩存導致的新版本發布后不生效的問題

          所以打包的時候常在文件后面加上 hash 值

          然后在 HTML 文件中的資源引入地址也需要換成 CDN 服務提供的地址

          /alicdn/xx12dsa311.js


          # 利用不同域名的 cdn 去存放資源, (tcp連接限制)

          webpack 構建時添加 cdn

          // 靜態資源的導入 URL 需要變成指向 CDN 服務的絕對路徑的 URL 而不是相對于 HTML 文件的 URL。

          // 靜態資源的文件名稱需要帶上有文件內容算出來的 Hash 值,以防止被緩存。

          // 不同類型的資源放到不同域名的 CDN 服務上去,以防止資源的并行加載被阻塞。

          module.exports = {

           // 省略 entry 配置...

           output: {

             // 給輸出的 JavaScript 文件名稱加上 Hash 值

             filename: '[name]_[chunkhash:8].js',

             path: path.resolve(__dirname, './dist'),

             // 指定存放 JavaScript 文件的 CDN 目錄 URL

             publicPath: '//js.cdn.com/id/',

           },

           module: {

             rules: [

               {

                 // 增加對 CSS 文件的支持

                 test: /\.css$/,

                 // 提取出 Chunk 中的 CSS 代碼到單獨的文件中

                 use: ExtractTextPlugin.extract({

                   // 壓縮 CSS 代碼

                   use: ['css-loader?minimize'],

                   // 指定存放 CSS 中導入的資源(例如圖片)的 CDN 目錄 URL

                   publicPath: '//img.cdn.com/id/'

                 }),

               },

               {

                 // 增加對 PNG 文件的支持

                 test: /\.png$/,

                 // 給輸出的 PNG 文件名稱加上 Hash 值

                 use: ['file-loader?name=[name]_[hash:8].[ext]'],

               },

               // 省略其它 Loader 配置...

             ]

           },

           plugins: [

             // 使用 WebPlugin 自動生成 HTML

             new WebPlugin({

               // HTML 模版文件所在的文件路徑

               template: './template.html',

               // 輸出的 HTML 的文件名稱

               filename: 'index.html',

               // 指定存放 CSS 文件的 CDN 目錄 URL

               stylePublicPath: '//css.cdn.com/id/',

             }),

             new ExtractTextPlugin({

               // 給輸出的 CSS 文件名稱加上 Hash 值

               filename: `[name]_[contenthash:8].css`,

             }),

             // 省略代碼壓縮插件配置...

           ],

          };

          /*

          以上代碼中最核心的部分是通過 publicPath 參數設置存放靜態資源的 CDN 目錄 URL,

          為了讓不同類型的資源輸出到不同的 CDN,需要分別在:


          output.publicPath 中設置 JavaScript 的地址。

          css-loader.publicPath 中設置被 CSS 導入的資源的的地址。

          WebPlugin.stylePublicPath 中設置 CSS 文件的地址。

          設置好 publicPath 后,WebPlugin 在生成 HTML 文件和 css-loader 轉換 CSS 代碼時,會考慮到配置中的 publicPath,用對應的線上地址替換原來的相對地址。

          */

          參考

          DNS MDN]

          webpack 文檔

          深入淺出 Webpack

          Scope Hoisting



          內嵌iframe頁面在IOS下會受內部元素影響自動撐開的問題

          seo達人

          IOS下的webview頁面,內嵌iframe元素,將其樣式指定為寬高100%:

          .iframe { width: 100%; height: 100%;
          }

          在安卓下運行均無問題,但是在IOS下會出現異常。

          具體表現為iframe頁面內的子元素一旦超出原先的邊界,只要能影響到html元素的寬高,就會自動撐開iframe,即使html元素設置了overflow:hidden也沒用。
          比如一個body元素下的彈層需要從下往上滑動進場,這個彈層的位置就會導致html高度的變化,因此頁面底部的tabbar就會在彈層運動期間先消失再出現。

          解決方法就是使用具體的寬高數值鎖定iframe元素:

          function onLoadIFrame (index) { // 修復IOS下輪播圖初始化瞬間會讓iframe寬度自行擴大問題 if (this.ENV.isIOS) { const iframe = this.$el.querySelector('#iframe' + index)
              iframe.style.width = iframe.clientWidth + 'px' iframe.style.height = iframe.clientHeight + 'px' }
          }

          日歷

          鏈接

          個人資料

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

          存檔

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