記錄--N 個值得一看的前端程式碼片段

林恒發表於2024-06-14

🧑‍💻 寫在開頭

點贊 + 收藏 === 學會🤣🤣🤣

在日常的開發過程中,我們都會有一些常用的程式碼片段,這些程式碼片段可以直接複製到各個專案中使用,非常方便。如果你有接手過別人的專案,就可以很明顯感受到幾個專案一般都會有一些相同的工具類方法,這些方法就是之前開發者的常用程式碼片段。

現在前端社群相當完善,有許多好用質量又有保證的庫,如 lodash、dayjs、classnames、js-cookie 等,這些庫基本能滿足你開發中對陣列、日期、類名、cookie 等的處理。

所以本文會盡量不重複介紹那些很常見的程式碼片段。

1. 檢測元素之外的點選

在實現隱藏彈窗或收起下拉框時,如果你還在一層層判斷是否點選了某個元素之外的區域,趕緊試試使用 contains 方法來實現。

document.addEventListener('click', function (evt) {
    // isClickedOutside 為 true 如果點選的元素在 ele 之外
    const isClickedOutside = !ele.contains(evt.target);
});

2. 快速開啟官網

當你想檢視第三方庫的主頁和程式碼倉庫時,你可以使用一下命令快速開啟:

// 開啟主頁
npm home PACKAGE_NAME
npm home react

// 開啟程式碼倉庫
npm repo PACKAGE_NAME
npm repo react

3. 一次性的事件監聽

除了在監聽的事件函式中移除當前的監聽外,也可以使用 once 引數。

const handler = function (e) {};
ele.addEventListener('event-name', handler, { once: true });

4. 格式化時分秒

在展示音影片時長之類的場景時,需要把時長秒數格式為 HH:mm:ss 的格式。

const formatSeconds = (s) =>
  [parseInt(s / 60 / 60), parseInt((s / 60) % 60), parseInt(s % 60)]
    .join(':')
    .replace(/\b(\d)\b/g, '0$1')

如果你想顯示“剛剛”、“5分鐘前”之類的內容,可以嘗試 timeago.js 庫。

5. URL 引數轉化為物件

獲取 url 引數有個熱門的庫 query-string,如果不想使用的話,可以透過 URLSearchParams API 實現。

const getUrlParams = (query) =>
  Array.from(new URLSearchParams(query)).reduce(
    (p, [k, v]) =>
      Object.assign({}, p, { [k]: p[k] ? (Array.isArray(p[k]) ? p[k] : [p[k]]).concat(v) : v }),
    {}
  )
  
// 獲取 query 引數
getUrlParams(location.query)
// { a: ['1', '4'], b: '2', c: '3' }
getUrlParams('?a=1&b=2&c=3&a=4')

// 獲取 hash 引數
getUrlParams(location.hash.split('?')[1])

6. 開啟新頁籤

看似平平無奇的開啟頁籤,但是需要關注下 rel,如果要開啟外鏈,建議設定為 noopener noreferrer,避免一些惡意網站透過 window.opener.location 重定向你的網站地址。window.open 方法同理。

// 高版本瀏覽器 rel 預設為 noopener,不過建議顯示設定,相容低版本。
<a target="_blank" rel="noopener noreferrer">...</a>

// window.open rel 預設為 opener,需要自己設定
window.open('https://baidu.com', 'baidu', 'noopener,noreferrer')

// 以下有安全漏洞,開啟的新頁籤可以透過 window.opener.location 重定向你的網站
<a target="_blank" rel="opener">...</a>
window.opener.location = 'http://fake.website.here';

7. 顯示上傳的圖片

透過 fileReader API 的 readAsDataURL 方法來顯示上傳圖片

function readImage() {
  const fileReader = new FileReader()
  const file = document.getElementById('uploaded-file').files[0]

  if (file) {
    fileReader.readAsDataURL(file)
  }

  fileReader.addEventListener(
    'load',
    () => {
      const result = fileReader.result
      const resultContainer = document.getElementById('result')
      const img = document.createElement('img')
      img.src = result
      resultContainer.append(img)
    },
    { once: true }
  )
}

8. 檔案下載

使用 a 標籤的 download 屬性,同源才能觸發下載,IE 不支援,移動端相容性也不太好。

<a href="/path/to/file" download>Download</a>

// 或者 js 臨時生成 a
function download(url) {
  const link = document.createElement('a')
  link.download = 'file name'
  link.href = 'url'

  document.body.appendChild(link)
  link.click()
  document.body.removeChild(link)
}
靜態資源伺服器設定響應頭也能觸發瀏覽器下載。
Content-Disposition: attachment; filename="filename.jpg"

除了線上檔案下載,你還可以建立一個 text 或 json 檔案,並下載,主要用到了 Blob 物件和 createObjectURL 方法。

const data = JSON.stringify({ 'message': 'Hello Word' });

const blob = new Blob([data], { type: 'application/json' });

// 建立一個 URL
const url = window.URL.createObjectURL(blob);

// 用上面的 download 方法下載這個 url
...

// 釋放建立的 URL
window.URL.revokeObjectURL(url);

9. 快取結果

快取函式的結果,當計算比較複雜時可以使用。

const memoize = (fn) =>
  (
    (cache = Object.create(null)) =>
    (arg) =>
      cache[arg] || (cache[arg] = fn(arg))
  )()

10. 多行省略號

單行或多行截斷顯示省略號,很常用的 CSS 片段。

.truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.truncate {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

11. 選中最後幾個元素

// 前三個
li:nth-child(-n + 3) {
  text-decoration: underline;
}

// 選中 2-5 的列表項
li:nth-child(n + 2):nth-child(-n + 5) {
  color: #2563eb;
}

// 倒數兩個
li:nth-last-child(-n + 2) {
  text-decoration-line: line-through;
}

12. 捲軸樣式

自定義捲軸樣式也是很常見的需求,除了透過樣式,也可以透過第三方庫(如 better-scroll 等)來實現自定義捲軸樣式。

/*定義捲軸高寬及背景 高寬分別對應橫豎捲軸的尺寸*/
::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}

/*定義捲軸軌道 內陰影+圓角*/
::-webkit-scrollbar-track {
  border-radius: 10px;
  background-color: #fafafa;
}

/*定義滑塊 內陰影+圓角*/
::-webkit-scrollbar-thumb {
  border-radius: 10px;
  background: rgb(191, 191, 191);
}

// 較新的 API
body {
  scrollbar-width: thin;
  scrollbar-color: #718096 #edf2f7;
}

13. 百分比計算 - 最大餘額法

計算百分比時,由於四捨五入,各個比例相加可能不等於 1,透過最大餘額法可以保證總數為 1。

// 輸出 ['32.56%', '6.97%', '27.91%', '32.56%']
getPercentWithPrecision([56, 12, 48, 56], 2)

// 具體最大餘額法演算法可以網上搜尋檢視
function getPercentWithPrecision(valueList, precision) {
  // 根據保留的小數位做對應的放大
  const digits = Math.pow(10, precision)
  const sum = valueList.reduce((total, cur) => total + cur, 0)
  
  // 計算每項佔比,並做放大,保證整數部分就是當前獲得的席位,小數部分就是餘額
  const votesPerQuota = valueList.map((val) => {
      return val / sum * 100 * digits
  })
  // 整數部分就是每項首次分配的席位
  const seats = votesPerQuota.map((val) => {
    return Math.floor(val);
  });
  // 計算各項的餘額
  const remainder = votesPerQuota.map((val) => {
    return val - Math.floor(val)
  })
    
  // 總席位
  const totalSeats = 100 * digits
  // 當前已經分配出去的席位總數
  let currentSeats = votesPerQuota.reduce((total, cur) => total + Math.floor(cur), 0)
    
  // 按最大餘額法分配
  while(totalSeats - currentSeats > 0) {
    let maxIdx = -1 // 餘數最大的 id
    let maxValue = Number.NEGATIVE_INFINITY // 最大餘額, 初始重置為無窮小

    // 選出這組餘額資料中最大值
    for(var i = 0; i < remainder.length; i++) {
      if (maxValue < remainder[i]) {
        maxValue = remainder[i]
        maxIdx = i
      }
    }
        
    // 對應的項席位加 1,餘額清零,當前分配席位加 1
    seats[maxIdx]++
    remainder[maxIdx] = 0
    currentSeats++
  }
    
  return seats.map((val) => `${val / totalSeats * 100}%`)
}

14. 限制併發

當有大量請求需要發起時,往往需求限制併發數量保證其他請求能優先返回。

async function asyncPool(poolLimit, iterable, iteratorFn) {
  // 用於儲存所有非同步請求
  const ret = [];
  // 使用者儲存正在進行的請求
  const executing = new Set();
  for (const item of iterable) {
    // 構造出請求 Promise
    const p = Promise.resolve().then(() => iteratorFn(item, iterable));
    ret.push(p);
    executing.add(p);
    // 請求執行結束後從正在進行的陣列中移除
    const clean = () => executing.delete(p);
    p.then(clean).catch(clean);
    // 如果正在執行的請求數大於併發數,就使用 Promise.race 等待一個最快執行完的請求
    if (executing.size >= poolLimit) {
      await Promise.race(executing);
    }
  }
  // 返回所有結果
  return Promise.all(ret);
}

// 使用方法
const timeout = i => new Promise(resolve => setTimeout(() => resolve(i), i));
asyncPool(2, [1000, 5000, 3000, 2000], timeout).then(results => {
  console.log(results)
})

15. uuid

生成 uuid 的程式碼片段

const uuid = (a) =>
  a
    ? (a ^ ((Math.random() * 16) >> (a / 4))).toString(16)
    : ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, uuid)

16. 開啟 Modal 時禁止 body 滾動

開啟彈窗的時候,會發現背後的內容還是可以滾動,我們需要在彈窗出現時禁用滾動,在彈窗消失時恢復。

// 開啟 Modal 時,禁止 body 滾動
document.body.style.overflow = 'hidden';

// 恢復滾動
document.body.style.removeProperty('overflow');

本文轉載於:https://juejin.cn/post/7371312967781777418

如果對您有所幫助,歡迎您點個關注,我會定時更新技術文件,大家一起討論學習,一起進步。

記錄--N 個值得一看的前端程式碼片段

相關文章