模擬面試、簡歷指導、入職指導、計畫指導、答疑解惑 可私信找我~已幫助100+名同學完成改造!
前言
大家好,我是林三心,用最通俗易懂的話講最難的知識點是我的座右銘,基礎是進階的前提是我的初心。
JS 繞不開的精度遺失問題
在 javascript 中,當我們進行運算時
0.1 + 0.2
你覺得輸出是 0.3 嗎?顯然不是的,由於 javascript 存在精度遺失問題,導致了輸出的不是你預期的
至於為什麽會精度遺失呢?我之前出過一篇文章專門講了這個原因你知道 0.1+0.2 !==0.3是進制問題,但你講不出個所以然,是吧?🐶,感興趣的朋友可以看看,由於這不是本文的重點,所以我在這就不過多講解~
解決精度遺失的方案?
我會選擇使用
decimal.js
這個庫,文件在 文件,他的基本使用如下:
// 先安裝
npm install decimal.js
// 後使用
const Decimal = require('decimal.js');
new Decimal(0.1).add(0.2) // 加法 輸出 0.3
new Decimal(0.1).sub(0.2) // 減法
new Decimal(0.1).mul(0.2) // 乘法
new Decimal(0.1).div(0.2) // 除法
使用
decimal.js
進行運算,能解決精度遺失的問題~
不想手動!想自動!
很煩啊!
當我們擁有了
decimal.js
之後,每當我們進行運算的時候,就必須引入它進行使用,每一個頁面都得重復這一操作,於是萌生了一個想法——我想自動!不想手動!
思路
那要怎麽才能自動呢?由於前段時間群裏很多人說想學習寫 babel 外掛程式,所以剛好,針對這個需求,我可以實作一個 babel 外掛程式,它的功能是:將計畫中
0.1 + 0.2
這種運算式,轉換為
new Decimal(0.1).add(0.2)
0.1 + 0.2
// 轉換為
new Decimal(0.1).add(0.2)
這樣就能一次性把計畫中的精度遺失問題解決了~
開發 babel 外掛程式
前置準備
涉及到三個問題:
webpack 和 rollup 如何選擇
rollup 打包環境的搭建
如何釋出到 npm 上
這三個問題具體我在上一篇文章【 】裏有提到過了,在本文我就不過多講解
搭建一個 Rollup 打包環境
新建一個
babel-plugin-sx-accuracy
資料夾,用來開發 babel 外掛程式
名字可以自己取,但是為了規範,最好是
babel-plugin-
開頭
接著進入
babel-plugin-sx-accuracy
資料夾,輸入
npm init
npm i rollup @rollup/plugin-babel -D
npm i decimal.js -S
package.json
中的內容為:
"name": "babel-plugin-sx-accuracy",
"version": "1.0.20",
"description": "",
"main": "dist/index.js",
"type": "module",
"scripts": {
"build": "rollup -c"
},
"files": [
"dist/*",
"src/*"
],
"author": "",
"license": "ISC",
"devDependencies": {
"@rollup/plugin-babel": "^6.0.3",
"rollup": "^3.26.2"
},
"dependencies": {
"decimal.js": "^10.4.3"
}
}
然後在根目錄下新建
rollup.config.js
檔,用來配置 rollup 打包
// rollup.config.js
import babel from'@rollup/plugin-babel';
exportdefault {
input: 'src/index.js',
output: {
file: 'dist/index.js',
format: 'cjs',
},
plugins: [
babel({
babelHelpers: 'bundled',
}),
],
};
最後新建
src/index.js
,我們的外掛程式邏輯就寫在這裏
什麽是抽象語法樹(AST)?
我們可以借助一個網站,來一睹抽象語法樹的真容~ https://astexplorer.net/
這裏我們可以記住幾個點
每一個程式碼片段都有屬於自己的節點型別
程式碼最外層的節點型別為
Program
像
0.1+0.2
這種運算式,節點型別為
BinaryExpression
BinaryExpression
節點裏會有幾個重要的東西
operaor:運算子號
left:左邊的數位
right:右邊的數位
其實抽象語法樹的節點型別有很多種,我列舉一些:
識別元(Identifier):表示變量、函式名等識別元的節點
字面量(Literal):表示字面量值,如字串、數位、布爾值等
運算式語句(ExpressionStatement):表示包含運算式的語句節點
賦值運算式(AssignmentExpression):表示賦值操作的運算式節點,如
x = 5
二元運算式(BinaryExpression):表示包含二元操作符的運算式節點,如
x + y
一元運算式(UnaryExpression):表示包含一元操作符的運算式節點,如
-x
函式聲明(FunctionDeclaration):表示函式聲明的節點,包括函式名、參數和函式體
變量聲明(VariableDeclaration):表示變量聲明的節點,包含變量名和可選的初始值
條件語句(IfStatement):表示 If 條件語句的節點,包括條件運算式、if 分支和可選的 else 分支
迴圈語句(WhileStatement、ForStatement):表示迴圈語句的節點,分別代表 While 迴圈和 For 迴圈
物件字面量(ObjectLiteral):表示物件字面量的節點,包含物件內容和內容值
陣列字面量(ArrayLiteral):表示陣列字面量的節點,包含陣列元素
函式呼叫(CallExpression):表示函式呼叫的節點,包含呼叫的函式名和參數列
返回語句(ReturnStatement):表示返回語句的節點,包含返回的運算式
當然大家現階段不需要去記,大家只需要記得這兩個型別就行了:
程式碼最外層的節點型別為
Program
像
0.1+0.2
這種運算式,節點型別為
BinaryExpression
其實,我們平時在 webpack 開發時會接觸到一系列的外掛程式,他們的功能比如有
去除 console.log
壓縮程式碼
去除註釋
其實他們的原理整體上都是一致的,分為三步:
第一步:將程式碼轉換成抽象語法樹
第二步:使用 babel 為我們提供的方法,對語法樹進行增刪改查
第三步:將處理後的語法樹重新轉換成程式碼
而我們將要開發的外掛程式,也是用到這個過程,但是第一步和第三步我們不需要管,我們只需要完成第二步中的增刪改查操作即可~
註意點:在第二步中,babel 會對抽象語法樹進行深度遍歷,遍歷到目標節點後,又會重新回到上層節點去重新遍歷下一個目標節點,所以一個節點會被遍歷兩次,一來一回 進去是
enter
回去是
exit
外掛程式基本程式碼結構
下文使用
AST
來表達抽象語法樹
exportdefaultfunction ({ template: template, types: t }) {
return {
visitor: {
Program: {
exit: function (path) {
}
},
BinaryExpression: {
exit: function (path) {
}
}
}
}
}
開發一個 babel 外掛程式,檔必須預設返回一個函式,接收一個物件參數,裏面有個內容我們需要用到
template:
是
@babel/template
的一個方法,他能使用樣版的方式生成AST節點
函式內部的東西,我們也介紹下
vistor:
你可以理解為修改AST節點的
入口
Program、BinaryExpression: 你需要修改的AST節點型別
exit:
就是剛剛說的
一來一回
中的,
回
path: 就是被遍歷到的AST節點物件
外掛程式完全實作
// 定義建構函式的名稱常量
const DECIMAL_FUN_NAME = 'Decimal'
// 運算子號對映 decimal.js 的四個方法
const OPERATIONS_MAP = {
'+': 'add',
'-': 'sub',
'*': 'mul',
'/': 'div'
}
// 運算子號陣列
const OPERATIONS = Object.keys(OPERATIONS_MAP)
exportdefaultfunction ({ template: template }) {
// require decimal.js 的節點樣版
const requireDecimalTemp = template(`const ${DECIMAL_FUN_NAME}=require('decimal.js')`);
// 將運算運算式轉換為decimal函式的節點樣版
const operationTemp = template(`new ${DECIMAL_FUN_NAME}(LEFT).OPERATION(RIGHT).toNumber()`);
return {
visitor: {
Program: {
exit: function (path) {
// 呼叫方法,往子節點body
// 中插入 const Decimal = require('decimal.js')
// 運算式
path.unshiftContainer("body",
requireDecimalTemp())
}
},
BinaryExpression: {
exit: function (path) {
const operator = path.node.operator;
if (OPERATIONS.includes(operator)) {
// 呼叫方法替換節點
path.replaceWith(
// 傳入 operator left right
operationTemp({
LEFT: path.node.left,
RIGHT: path.node.right,
OPERATION: OPERATIONS_MAP[operator]
})
)
}
}
}
}
}
}
打包 & 釋出 NPM
當開發完成後,我們先
npm run build
進行打包
然後執行
npm publish
釋出到 NPM 上
計畫使用
首先安裝
babel-plugin-sx-accuracy
npm i babel-plugin-sx-accuracy
只需要在計畫中的
.babelrc
或者
babel.config.js
中加入
babel-plugin-sx-accuracy
即可
{
"presets": ["@babel/preset-env"],
"plugins": ["babel-plugin-sx-accuracy"]
}
我們來試試,一開始程式碼是
console.log(0.1 + 0.2)
console.log(0.3 - 0.1)
console.log(0.2 * 0.1)
console.log(0.3 / 0.1)
打包後我們看看產物,並且輸出的也都是沒有精度遺失的結果!!!
結語
我是林三心
一個待過 小型toG型外包公司、大型外包公司、小公司、潛力型創業公司、大公司 的作死型前端選手;
一個偏前端的全幹工程師;
一個不正經的金塊作者;
逗比的B站up主;
不帥的小紅書博主;
喜歡打鐵的籃球菜鳥;
喜歡歷史的乏味少年;
喜歡rap的五音不全弱雞
如果你想一起學習前端,一起摸魚,一起研究簡歷最佳化,一起研究面試進步,一起交流歷史音樂籃球rap,可以來俺的摸魚學習群哈哈,點這個,有7000多名前端小夥伴在等著一起學習哦 -->
廣州的兄弟可以約飯哦,或者約球~我負責打鐵,你負責進球,謝謝~
模擬面試、簡歷指導、入職指導、計畫指導、答疑解惑 可私信找我~已幫助100+名同學完成改造!