• <menu id="sssag"></menu>
  • <menu id="sssag"></menu>
  • 前言

    因為比較菜,所以經常需要讀一些別人的代碼學習學習。

    有源碼的代碼當然好,但是很多網站不開源。這些網站的 js 又都是打包壓縮過的,學習起來很難受。

    所以我做了一個小工具,通過修改抽象語法樹,來處理這些打包壓縮過的 js,增強代碼可讀性,讓我們學習起來更容易。

    如果再借助重定向線上 js 到本地 js,或者使用 chrome 自帶的 override 源碼能力,甚至可以輕松調試別人的線上代碼。

    有了這個工具,我 CV 界大師兄的名號可謂實至名歸。

    下面是這個工具的代碼倉庫:boompack。

    需求

    在此之前,其實面對這些壓縮過的 js 我是不太想做這個工具的。

    通常使用 prettier 美化一下,然后慢慢磨就好了。

    但是這次我面對的是一個 canvas 相關的 js,壓縮后的核心代碼使用 prettier 格式化之后有 2 萬多行,看到這份代碼之后人都麻了。

    這里隨便寫個壓縮代碼示例來舉例:

    function f() {
      var a = (c = 33, d = 12), b = 1, g = (e == 2 ? a === 1 && b == 1 || c == 1 && d == 1 && c == 4 : c = 2);
      for (var i; i < 10; i++)if (s < 1) s++
      return a = 2, d == 2, e = !1, e = !0
    }
    

    這份代碼其實并不算特別復雜,因為沒有十幾個邏輯表達式和三元運算交雜在一起。

    但是這份代碼很典型,因為基本上比較影響閱讀的點都有。

    我們簡單列一下:

    • 大量無意義的單字符變量,修改變量名也無法批量替換
    • 序列表達式很多,即大量語句以逗號分隔,調試時只算做一行代碼
    • 多個邏輯表達式混雜不清,需要改為 if 表達式
    • if 或者 for 循環不加花括號
    • 大量三元運算,需要使用 if 和 else
    • !0 和!1 的表達反人類,需要使用 true 和 false
    • return 語句結合序列表達式,實際上只返回最后一個

    這些就是主要的困難,特別是當它們各種互相嵌套,又和十幾個邏輯運算和三元運算交雜在一起,光是拆解出來就得花個十幾分鐘。

    如果人力拆解這些代碼,也不是沒有好處,至少你可以化身人肉低端編譯器,反復鞏固 js 基礎,要是碰到一些奇葩公司讓你手寫代碼你就是王者。

    但是因為我需要留出時間打游戲的原因,所以還是寫了這么個工具簡化流程。

    使用方法

    • 克隆倉庫到本地后
    • yarn install 安裝依賴包
    • 將需要轉換地壓縮代碼,復制粘貼到test/from/index.js這個文件中
    • 終端運行腳本 yarn start
    • 最終會在test/to/這個文件夾下生成 index.js,也就是我們最后修改后的文件。

    效果

    使用工具轉換后的 js 代碼如下:

    function func_f() {
      let var_a, var_b, var_g;
      c = 33;
      var_a = d = 12;
      var_b = 1;
    
      if (e == 2) {
        if (var_a === 1 && var_b == 1) {
          if (var_a === 1) {
            var_g = var_b == 1;
          } else {
            var_g = var_a === 1;
          }
        } else {
          if (c == 1 && d == 1) {
            var_g = c == 4;
          } else {
            if (c == 1) {
              var_g = d == 1;
            } else {
              var_g = c == 1;
            }
          }
        }
      } else {
        var_g = c = 2;
      }
    
      for (var var_i; var_i < 10; var_i++) {
        if (s < 1) {
          s++
        }
      }
      let result;
      var_a = 2;
      d == 2;
      e = false;
      result = e = true;
      return result;
    }
    

    可以看到相對于壓縮后的代碼,我們轉換后的代碼變長了很多。

    這份代碼相較于上一份,可讀性大大增強了。

    另外我已經使用 jQuery 壓縮后的文件測試過了,轉換沒有任何問題。

    然而,依然不保證轉換后的代碼一定正確,js 的 hack 玩法太多,只能說用這個轉換肯定可控。

    核心玩法:抽象語法樹

    想要解析修改這種壓縮 js,需要用到我們的抽象語法樹。

    所謂抽象語法樹,實際上就是一種樹形結構來表示編程語句。

    具體可以百度,這里不解釋太多,總之你可以理解為可以將一串代碼解析成一個樹形結構,這個樹形結構上面每個節點代表一種語法結構。

    這里列一個必備網站:https://astexplorer.net/,用來查看 js 被轉換為抽象語法樹后的樣子。

    現在前端的基礎庫 babel 系列,就是通過抽象語法樹將 es6 轉換為 es5 的,當然也包括轉換 reacttypescript。

    因為抽象語法樹和代碼之間是可以相互轉換的。

    所以我們的核心思路是將代碼轉換為抽象語法樹,然后在這個樹上做修改,修改完后再轉換為代碼。

    應用 recast 去轉換代碼

    js 代碼和抽象語法樹的轉換有很多 js 庫可以實現。

    比如@babel/parser,recast,還有不少其他的庫,這里我們使用 recast。

    我對這個研究也不深入,沒怎么了解他們的優缺點,不過當時看到 recast滿足需求就直接用了。

    可以在 npmjs 上找到 recast,里面有簡單的介紹文檔:地址,也有倉庫地址。

    但是 recast 的文檔不太夠,有的關鍵點還得自己看下具體的示例和源碼才能弄明白,不過也不難。

    這里就不展開了,先上一段我自己寫的簡單代碼:

    import { parse, print } from "recast";
    import { readFile, writeFile } from "fs";
    import path from "path";
    import modifyAst from "./utils/modifyAst.js";
    
    const fromPath = path.join("./test/from/index.js");
    const toPath = path.join("./test/to/index.js");
    
    readFile(fromPath, { encoding: "utf8" }, (err, sourceCode) => {
        // 通過recast的parse函數轉換為ast語法樹
        const ast = parse(sourceCode);
        modifyAst(ast);
        writeFile(toPath, print(ast).code, () => {
            console.info("搞完");
        });
    });
    

    這段代碼的用處是從 from 文件夾下的文件獲取 js 代碼后,通過 recastparse 函數轉換為 ast語法樹 ,再通過我自定義的函數 modifyAst 來修改語法樹后,最后使用 recastprint 函數將 ast語法樹 轉換為 js 代碼。

    這段內容比較簡單,主要就是借助 recast 將代碼轉成抽象語法樹,再轉回代碼。

    具體修改抽象語法樹在 modifyAst 里面:

    import addBlock from "./addBlock.js";
    import modifyReturn from "./modifyReturn.js";
    import modifyUnaryExpression from "./modifyUnaryExpression.js";
    // 修改聲明中的表達式
    import replaceVarName from "./modifyVariableDeclaration/replaceVarName.js";
    import modifyDeclarationInit from "./modifyVariableDeclaration/modifyDeclarationInit.js";
    
    // 修改表達式
    import modifyExpressionStatement from "./modifyExpressionStatement/index.js";
    
    /**
    * 修改抽象語法樹
    */
    const modifyAst = (ast) => {
      modifyUnaryExpression(ast);
      replaceVarName(ast);
      addBlock(ast);
      modifyReturn(ast);
      modifyDeclarationInit(ast);
      modifyExpressionStatement(ast);
    };
    
    export default modifyAst;
    

    modifyAst 中,我將不同的語句修改按照功能進行了劃分到,寫在了不同的文件中。

    本篇博客也不宜展開過多,我只挑一部分代碼展示:

    import { types, visit } from "recast";
    
    const { blockStatement } = types.builders;
    
    /**
    * 找到所有的if和for語句,給他們增加花括號
    * @param {抽象語法樹} ast
    */
    const addBlock = (ast) => {
      visit(ast, {
        // 找到所有的if語句給他們增加花括號
        visitIfStatement: function (path) {
          if (
            path.node.consequent != null &&
            path.node.consequent.type != "BlockStatement"
          ) {
            path.node.consequent = blockStatement([path.node.consequent]);
          }
          if (
            path.node.alternate != null &&
            path.node.alternate.type != "BlockStatement"
          ) {
            path.node.alternate = blockStatement([path.node.alternate]);
          }
          this.traverse(path);
        },
      });
    };
    
    export default addBlock;
    

    上面這部分代碼的作用是遍歷抽象樹中所有的 if 語句,給那些沒加花括號的 if 語句加上花括號。

    實際上就是使用 recastvisit 方法遍歷抽象語法樹。visitIfStatement 這個回調函數,就是在遍歷到 if 語句后執行的函數。

    在函數中有兩個 if 語句,那就是判斷以及修改的代碼,這個不多講。

    需要注意的是,recast 遍歷抽象語法樹時,如果識別到 if 語句后,不會繼續遍歷這個 if 語句里包裹的 if 語句,所以這里使用

    this.traverse(path);
    

    這行代碼是用來繼續遍歷當前節點的子節點的,繼續往下找 if 語句。

    如果你自己判斷出不需要向下遍歷,不能簡單地刪掉這段代碼,需要用這行代碼替換:

    return false
    

    返回 false 表示不再向下遍歷。

    另外如果此時想直接使用新語句替換當前語句,可以直接返回一個新語句,例如:

    return literal(true);
    

    總結

    總的來說,做完這個小工具算是解放了我大把的時間。

    但是它只是我遇到典型壓縮代碼后,針對性進行更改的結果??赡苡龅揭恍┢渌麎嚎s后的語法,效果不大好,您也可以針對相應語法自行修改。

    當然,如果您有更好的方法和建議,也希望能不吝賜教。

    posted on 2022-03-07 16:06  韓子盧  閱讀(2277)  評論(7編輯  收藏  舉報



    国产在线码观看超清无码视频,人妻精品动漫H无码,十大看黄台高清视频,国产在线无码视频一区二区三区,国产男女乱婬真视频免费,免费看女人的隐私超爽,狠狠色狠狠色综合久久蜜芽