聊聊編碼那些事,順帶實現base64

目錄

前言

日常工作中,頻繁的使用base64取代小圖標,以便減少HTTP請求進而達到性能優化的目的。基於此來聊聊編碼的發展、爲什麼需要base64以及如何實現base64。此文章首發於聊聊編碼那些事,順帶實現base64轉載請註明來源。

進制間的轉換

一些前置知識,也涉及到一些經典的面試小題。

對任意進制的數進行任意進制轉換

Number.prototype.toString(radix)

將任意進制數轉換爲十進制數

parseInt(string, radix)

幾道關於parseInt的面試題

說到parseInt,不得不提到一個很有意思的面試題

// 會輸出什麼?
[1, 2, 3].map(parseInt)
// => 1, NaN, NaN

map方法第一個參數爲函數,函數有三個參數,array.map((item, index, array) => { ... })

實際上相當於

function fn (item, index) {
  return parseInt(item, index)
}
[1, 2, 3].map(fn)
// parseInt迭代過程相當於如下
// parseInt(1, 0) => 1
// parseInt(2, 1) => NaN
// parseInt(3, 2) => NaN

再來看一個類似的面試題

// 會輸出什麼?
'1 2 3'.replace(/\d/g, parseInt)
// => 1, NaN, 3

replace方法第二個參數若是一個函數,函數會有若干個參數。第一個爲匹配模式的字符串;第二個爲與模式中子表達式匹配的字符串,可以有零個或多個這樣的參數。

實際上相當於如下

function fn (...args) {
  // 只會取前兩個參數
  return parseInt(args[0], args[1])
}
'1 2 3'.replace(/\d/g, fn)
// parseInt迭代過程相當於如下
// parseInt('1', 0) => 1
// parseInt('2', 2) => NaN
// parseInt('3', 4) => 3

其實在mdn中對parseInt/map/replace已經講解的很詳細,期望大家在工作之餘不要太過浮躁,別做伸手黨,靜下心來啃一下文檔並多做實踐,很多面試題自然會迎刃而解。

編碼發展歷史

ASCII

GBK2312

GBK

GB18030/DBCS

Unicode

UTF-8

現在的標準,有如下特點

  • UTF-8 就是在互聯網上使用最廣的一種 Unicode 的實現方式
  • UTF-8就是每次以8個位爲單位傳輸數據
  • 而UTF-16就是每次 16 個位
  • UTF-8 最大的一個特點,就是它是一種變長的編碼方式
  • Unicode 一箇中文字符佔 2 個字節,而 UTF-8 一箇中文字符佔 3 個字節
  • UTF-8 是 Unicode 的實現方式之一

base64編碼

爲什麼需要base64

在開發時,經常會有一些小圖標圖片,每一個圖片都會有一次HTTP請求,由於瀏覽器對同一個域名的併發數量有限制,所以我們應該儘可能減少HTTP請求個數。

本文主要講解編碼相關,那就只講解從編碼入手如何去減少HTTP請求。

在計算機內部,任何信息最終都是使用一系列二進制存儲,圖片也不例外。

而且在img標籤的src屬性後跟上一個base64字符,如果該字符有效,那麼會正常顯示圖片。

如何實現base64

以下涉及的所有代碼均在倉庫中,感興趣的可以自取。

讀取buffer轉爲json對象

首先準備一個2.txt文件。

馮蘭蘭啊我說今晚月色這麼美,你說是的。

case.js代碼

const fs = require('mz/fs')
const path = require('path')

// 讀取成buffer對象
async function read2JSON () {
   let ret = await fs.readFile(path.resolve(__dirname, '2.txt'))
   console.log(ret.toJSON())
   return ret.toJSON()
}
read2JSON()
// => { type: 'Buffer', data: [ 229, 134, 175, 229... ] }

上面的依賴mz/fs已經將fs都包裝成promise,所以我們能寫的更像同步。

readFile函數如果第二個參數沒有指定會讀取成一個buffer流,是由一個個16進制數組合在一起的。

buffer.toJSON可以將一個buffer流轉爲一個json對象,十六進制也會被轉十進制。如上輸出所示。

將10進制轉爲2進制

十進制轉爲二進制可以通過Number.toString(2)方法

// 將10進制轉爲2進制
async function data2b () {
  let data = await read2JSON()
  let ret = []
  data.data.forEach(item => {
    ret.push(item.toString(2))
  })
  console.log(ret)
  return ret
}
data2b()
// => [ '11100101', '10000110', '10101111', '11100101'...]

將2進制拼一起3*8然後分隔成4*6

一個漢字在UTF-8規範中由三個字節組成,一個字節由8個二進制物理位構成。所以一個漢字實際佔用內存3*8base64中我們實際需要6個物理位表示一個字節即2**6,所以做重新分割4*6

async function split () {
  let data = await data2b()
  let dataStr = data.join('')
  let ret = []

  let splitUnit = 6
  let flag = 0
  while (flag < dataStr.length) {
    ret.push(dataStr.substr(flag, splitUnit))
    flag = flag + splitUnit
  }
  console.log(ret)
  return ret
}
split()
// => [ '111001', '011000', '011010', '101111'...]

然後將2進制轉成10進制

二進制轉爲十進制可以通過parseInt(string, 2)方法

async function data20 () {
  let data = await split()
  let ret = data.map(item => {
    return parseInt(item, 2)
  })
  console.log(ret)
  return ret
}
data20()
// => [ 57, 24, 26, 47, 57, 24, 22, 48, 57, 24, 22, 48 ]

base64碼

base64中的64實際上是根據2**6所來,表示則由大寫字母、小寫字母、數字、+/構成。

const lowerCases = 'abcdefghijklmnopqrstuvwxyz'
const numbers = '0123456789'
const base64lib = `${lowerCases.toUpperCase()}${lowerCases}${numbers}+/`
console.log(base64lib)
// => ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

取到每一個base64碼

然後我們則可以取到每一個base64

async function main () {
  let data = await data20()
  let ret = []
  data.forEach(item => {
    ret.push(base64lib[item])
  })
  console.log(ret.join(''))
  return ret.join()
}

main()
// => 5Yav5YWw5YWw5ZWK5oiR6K+05LuK5pma5pyI6Imy6L+Z5LmI576O77yM5L2g6K+05piv55qE44CC

我們可以前往base64在線轉碼解碼進行驗證。

encoding-base64-decode

簡化代碼

對以上思路進行代碼簡化

const CHARTS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
function transfer (str) {
  let buf = Buffer.from(str)
  let result = ''
  for(let b of buf){
    result += b.toString(2)
  }
  return result.match(/(\d{6})/g).map(val => parseInt(val, 2)).map(val => CHARTS[val]).join('')
}
let fl = transfer('馮')
console.log(fl) // => 5Yav

小結

如上我們可以實現將中文轉爲base64,同理我們也可以轉換圖片。

async function read2JSON () {
  // let ret = await fs.readFile(path.resolve(__dirname, '2.txt'))
  // 讀取圖片
  let ret = await fs.readFile(path.resolve(__dirname, '../assets/encoding-base64-example.png'))
  console.log(ret.toJSON())
  return ret.toJSON()
}

特別的: 由於將2進制拼一起3*8然後分隔成4*6,原來存儲一個漢字需要三個字節,現在需要四個字節存儲,所以轉換爲base64後會比之前大3/1

下面笑臉圖片則是由img的src屬性展示的,不過本文並沒有實現圖片轉base64,因爲其邏輯較爲複雜,但是本文講解了大致思路,感興趣的可再做深究。

encoding-base64-example

encoding-base64-code

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章