當前位置: 妍妍網 > 碼農

寫了個外掛程式,一口氣解決計畫中所有精度遺失問題!

2024-04-26碼農

模擬面試、簡歷指導、入職指導、計畫指導、答疑解惑 可私信找我~已幫助100+名同學完成改造!

前言

大家好,我是林三心,用最通俗易懂的話講最難的知識點是我的座右銘,基礎是進階的前提是我的初心。

JS 繞不開的精度遺失問題

在 javascript 中,當我們進行運算時

0.1 + 0.2

你覺得輸出是 0.3 嗎?顯然不是的,由於 javascript 存在精度遺失問題,導致了輸出的不是你預期的

image.png

至於為什麽會精度遺失呢?我之前出過一篇文章專門講了這個原因你知道 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 ,我們的外掛程式邏輯就寫在這裏

    image.png

    什麽是抽象語法樹(AST)?

    我們可以借助一個網站,來一睹抽象語法樹的真容~ https://astexplorer.net/

    image.png

    這裏我們可以記住幾個點

  • 每一個程式碼片段都有屬於自己的節點型別

  • 程式碼最外層的節點型別為 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

    image.png

    外掛程式基本程式碼結構

    下文使用 AST 來表達抽象語法樹

    exportdefaultfunction ({ template: template, types: t }{
    return {
    visitor: {
    Program: {
    exitfunction (path{
    }
    },
    BinaryExpression: {
    exitfunction (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: {
    exitfunction (path{
    // 呼叫方法,往子節點body
    // 中插入 const Decimal = require('decimal.js')
    // 運算式
    path.unshiftContainer("body",
    requireDecimalTemp())
    }
    },
    BinaryExpression: {
    exitfunction (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 上

    image.png

    計畫使用

    首先安裝 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)

    打包後我們看看產物,並且輸出的也都是沒有精度遺失的結果!!!

    image.png

    結語

    我是林三心

  • 一個待過 小型toG型外包公司、大型外包公司、小公司、潛力型創業公司、大公司 的作死型前端選手;

  • 一個偏前端的全幹工程師;

  • 一個不正經的金塊作者;

  • 逗比的B站up主;

  • 不帥的小紅書博主;

  • 喜歡打鐵的籃球菜鳥;

  • 喜歡歷史的乏味少年;

  • 喜歡rap的五音不全弱雞

  • 如果你想一起學習前端,一起摸魚,一起研究簡歷最佳化,一起研究面試進步,一起交流歷史音樂籃球rap,可以來俺的摸魚學習群哈哈,點這個,有7000多名前端小夥伴在等著一起學習哦 -->

    廣州的兄弟可以約飯哦,或者約球~我負責打鐵,你負責進球,謝謝~

    模擬面試、簡歷指導、入職指導、計畫指導、答疑解惑 可私信找我~已幫助100+名同學完成改造!