介紹
JavaScript 和 Babel 簡介
-
JavaScript 是一種廣泛應(yīng)用于 Web 開發(fā)的腳本語言,它最初是由網(wǎng)景公司的 Brendan Eich 開發(fā)的。JavaScript 具有易學(xué)易用、靈活、動(dòng)態(tài)等特點(diǎn),它能夠幫助開發(fā)者在 Web 應(yīng)用中實(shí)現(xiàn)豐富的交互和動(dòng)態(tài)效果。
-
然而,由于 JavaScript 的語法和特性不斷更新,舊版的瀏覽器和環(huán)境可能無法完全支持新的 JavaScript 特性,這就使得開發(fā)者在編寫 JavaScript 代碼時(shí)需要考慮兼容性問題。為了解決這個(gè)問題,Babel 應(yīng)運(yùn)而生。
-
Babel 是一個(gè) JavaScript 編譯器,它可以將最新版本的 JavaScript 代碼轉(zhuǎn)換成向下兼容的代碼,以便舊版的瀏覽器和環(huán)境可以運(yùn)行。Babel 的轉(zhuǎn)譯過程是基于 AST(抽象語法樹)的,它可以通過分析 AST 來理解源代碼的結(jié)構(gòu)和含義,然后進(jìn)行相應(yīng)的轉(zhuǎn)換。
-
Babel 的使用非常靈活,可以通過命令行、Webpack、Rollup 等多種方式來集成到項(xiàng)目中。開發(fā)者可以根據(jù)自己的需求來配置 Babel,選擇需要的插件和預(yù)設(shè),從而實(shí)現(xiàn)對不同版本的 JavaScript 代碼的轉(zhuǎn)譯。同時(shí),Babel 還提供了許多插件和預(yù)設(shè),可以實(shí)現(xiàn)更加復(fù)雜的轉(zhuǎn)譯和擴(kuò)展功能,例如轉(zhuǎn)譯 JSX、ES6 模塊化、裝飾器等。
-
總之,Babel 是一個(gè)非常重要的 JavaScript 工具,它可以幫助開發(fā)者更加輕松地編寫和維護(hù)兼容性更好的 JavaScript 代碼,同時(shí)也為 JavaScript 社區(qū)的發(fā)展做出了重要貢獻(xiàn)。
Babel 的歷史和發(fā)展
-
Babel 誕生于 2014 年,最初的名字是 6to5,它的初衷是為了解決 JavaScript 新特性向下兼容的問題。當(dāng)時(shí),ES6 標(biāo)準(zhǔn)已經(jīng)發(fā)布,但是很多瀏覽器和環(huán)境并不支持 ES6,這就使得開發(fā)者無法充分利用新特性。
-
6to5 利用了新的 JavaScript 特性,如箭頭函數(shù)、模板字符串等,將其轉(zhuǎn)換為舊的 ES5 代碼,以便兼容性更廣的瀏覽器和環(huán)境也能夠運(yùn)行。6to5 很快獲得了廣泛的關(guān)注和認(rèn)可,它的開發(fā)者們決定將其更名為 Babel,并將其轉(zhuǎn)變?yōu)橐粋€(gè)更加通用的 JavaScript 編譯器。
-
隨著時(shí)間的推移,Babel 逐漸發(fā)展成為一個(gè)功能強(qiáng)大、靈活易用的 JavaScript 工具。除了對新特性的轉(zhuǎn)譯,Babel 還可以處理 TypeScript、Flow、JSX 等不同的 JavaScript 方言。同時(shí),Babel 的插件系統(tǒng)也逐漸完善,開發(fā)者們可以自己編寫插件來定制轉(zhuǎn)譯過程,或者使用現(xiàn)成的插件來實(shí)現(xiàn)更加復(fù)雜的功能。
-
2015 年,Babel 正式發(fā)布了 6.0 版本,這個(gè)版本帶來了很多重要的變化,其中最顯著的是支持了 ES6 的新語法和特性,這使得開發(fā)者可以更加方便地使用 ES6 來編寫 JavaScript 代碼。此后,Babel 還不斷更新迭代,推出了支持 ES7、ES8 等新版本的 JavaScript 標(biāo)準(zhǔn)的轉(zhuǎn)譯,并增加了對一些實(shí)驗(yàn)性的 JavaScript 特性的支持。
-
目前,Babel 已經(jīng)成為了 JavaScript 社區(qū)中最流行、最重要的工具之一,它對 JavaScript 的發(fā)展和演進(jìn)做出了重要貢獻(xiàn)。
Babel 的優(yōu)勢和應(yīng)用場景
-
幫助解決兼容性問題:Babel 可以將最新版本的 JavaScript 代碼轉(zhuǎn)換為向下兼容的代碼,以便舊版的瀏覽器和環(huán)境可以運(yùn)行。這就幫助開發(fā)者解決了兼容性問題,使得開發(fā)者可以更加放心地使用最新的 JavaScript 特性來編寫代碼。
-
提高開發(fā)效率:Babel 可以自動(dòng)化地將新版本的 JavaScript 代碼轉(zhuǎn)換為向下兼容的代碼,使得開發(fā)者可以更加專注于編寫高質(zhì)量的代碼,而不需要考慮兼容性問題。這就大大提高了開發(fā)效率。
-
支持多種 JavaScript 方言:除了處理標(biāo)準(zhǔn)的 JavaScript 代碼,Babel 還可以處理 TypeScript、Flow、JSX 等不同的 JavaScript 方言。這就使得 Babel 成為了一個(gè)通用的 JavaScript 工具,能夠應(yīng)對各種不同的應(yīng)用場景。
-
插件系統(tǒng)功能強(qiáng)大:Babel 的插件系統(tǒng)非常靈活,可以自定義轉(zhuǎn)譯規(guī)則,也可以添加新的語法和特性。這就使得開發(fā)者可以根據(jù)自己的需求來配置 Babel,選擇需要的插件和預(yù)設(shè),實(shí)現(xiàn)對不同版本的 JavaScript 代碼的轉(zhuǎn)譯。
基于以上優(yōu)勢,Babel 的應(yīng)用場景非常廣泛,下面列舉幾個(gè)典型的應(yīng)用場景:
-
在前端開發(fā)中,Babel 可以幫助開發(fā)者更加方便地使用最新的 JavaScript 特性來編寫代碼,同時(shí)保證兼容性。例如,在使用 React 開發(fā) Web 應(yīng)用時(shí),Babel 可以將 JSX 轉(zhuǎn)換為 JavaScript 代碼。
-
在 Node.js 開發(fā)中,Babel 可以幫助開發(fā)者使用最新的 JavaScript 特性和方言,例如使用 TypeScript 來編寫 Node.js 應(yīng)用程序。同時(shí),Babel 還可以將 Node.js 應(yīng)用程序打包成支持多種環(huán)境的代碼。
-
在開發(fā)工具中,Babel 可以作為編譯器使用,例如將 JavaScript 代碼轉(zhuǎn)換為 ES5 或 ES6 代碼。同時(shí),Babel 還可以作為 Webpack 等構(gòu)建工具的插件,用于處理 JavaScript 代碼的轉(zhuǎn)譯和打包。
Babel 的原理和工作流程
Babel 的原理主要分為三個(gè)步驟:
-
解析(Parsing):Babel 首先會(huì)將 JavaScript 代碼解析成抽象語法樹(AST)。這個(gè)過程可以通過使用 Babylon 解析器或者其他支持解析 JavaScript 代碼的解析器來完成。解析的結(jié)果是一個(gè)包含 JavaScript 代碼的 AST。
-
轉(zhuǎn)換(Transformation):在轉(zhuǎn)換階段,Babel 將會(huì)遍歷 AST,對 AST 中的節(jié)點(diǎn)進(jìn)行修改或者刪除,并生成新的 AST。這個(gè)過程中,可以使用 Babel 插件來添加新的語法或者修改現(xiàn)有的語法。例如,可以使用 @babel/plugin-transform-arrow-functions 插件將 ES6 的箭頭函數(shù)轉(zhuǎn)換為 ES5 的函數(shù)表達(dá)式。
-
生成(Code Generation):在代碼生成階段,Babel 會(huì)將生成的新 AST 轉(zhuǎn)換回 JavaScript 代碼,并輸出到指定的文件中。這個(gè)過程中,Babel 會(huì)根據(jù)需要進(jìn)行縮進(jìn)和格式化,并且會(huì)將新生成的 JavaScript 代碼與原始代碼進(jìn)行比較,以便調(diào)試和測試。
安裝babel模塊
逆向解混淆,主要用到 Babel 的以下幾個(gè)功能包,本文也僅介紹以下幾個(gè)功能包:
- 安裝 Babel:運(yùn)行以下命令安裝 Babel 及其相關(guān)模塊:這里安裝了
@babel/core
和@babel/cli
兩個(gè)模塊,@babel/core
是 Babel 工具鏈的核心模塊,@babel/cli
是 Babel 命令行工具,可以在命令行中直接使用 Babel 進(jìn)行代碼轉(zhuǎn)譯等操作。
npm install @babel/core @babel/cli --save-dev
- 安裝 Babel 解析器:Babel 使用解析器將 JavaScript 代碼轉(zhuǎn)換為抽象語法樹(AST),以便進(jìn)行后續(xù)的轉(zhuǎn)換操作。運(yùn)行以下命令安裝 Babel 解析器:
npm install @babel/parser --save-dev
- 安裝 Babel 轉(zhuǎn)換器:Babel 使用轉(zhuǎn)換器對 AST 進(jìn)行轉(zhuǎn)換操作,可以添加、刪除、替換、修改節(jié)點(diǎn)等。@babel/traverse 是 Babel 提供的 AST 轉(zhuǎn)換工具,運(yùn)行以下命令進(jìn)行安裝:
npm install @babel/traverse --save-dev
- 安裝 Babel 代碼生成器:Babel 將轉(zhuǎn)換后的 AST 重新生成 JavaScript 代碼,以便進(jìn)行后續(xù)的編譯和執(zhí)行。@babel/generator 是 Babel 提供的 AST 代碼生成器,運(yùn)行以下命令進(jìn)行安裝:
npm install @babel/generator --save-dev
- 安裝 @babel/types 模塊,可以使用 npm 或 yarn 命令進(jìn)行安裝:
npm install @babel/types
- 安裝完成后,可以在項(xiàng)目中使用這些模塊進(jìn)行 JavaScript 代碼的轉(zhuǎn)譯、解析和生成等操作。例如,以下代碼演示了使用 Babel 解析器將一個(gè)字符串解析成 AST,并使用 Babel 代碼生成器將 AST 轉(zhuǎn)換成 JavaScript 代碼:
- 需要注意的是,Babel 的使用需要一定的編程經(jīng)驗(yàn)和 AST 知識(shí),因此建議在學(xué)習(xí) Babel 之前先掌握 JavaScript 和 AST 的相關(guān)知識(shí)。
const babelParser = require('@babel/parser');
const babelGenerator = require('@babel/generator').default;
const code = 'const a = 1;';
const ast = babelParser.parse(code);//將代碼解析成ast語法樹
const output = babelGenerator(ast, { /* options */ }, code);
console.log(output.code); // 輸出轉(zhuǎn)換后的代碼:const a = 1;
@babel/parser
解析代碼
@babel/parser
模塊提供了一個(gè)名為parse()
的函數(shù),用于將 ECMAScript 代碼解析成 AST。該函數(shù)接受兩個(gè)參數(shù):
-
code
:需要解析的代碼,一個(gè)字符串。 -
options
:解析選項(xiàng),一個(gè)對象。
例如,以下代碼可以解析一個(gè)簡單的 ECMAScript 代碼片段,并打印出 AST:
const { parse } = require('@babel/parser');
const code = 'const x = 1 + 2;';
const ast = parse(code);
console.log(JSON.stringify(ast, null, 2));
解析選項(xiàng)
@babel/parser
模塊的parse()
函數(shù)可以接受一個(gè)選項(xiàng)對象作為第二個(gè)參數(shù),用于指定解析器的行為。常用的選項(xiàng)包括:
-
sourceType
:指定代碼的來源類型,可以為 “script” 或 “module”。默認(rèn)為 “script”。 -
plugins
:指定解析器使用的插件列表,一個(gè)數(shù)組。默認(rèn)為空數(shù)組。 -
ranges
:是否在 AST 節(jié)點(diǎn)中包含節(jié)點(diǎn)位置信息,一個(gè)布爾值。默認(rèn)為 false。 -
locations
:是否在 AST 節(jié)點(diǎn)中包含源代碼位置信息,一個(gè)布爾值。默認(rèn)為 false。 -
onComment
:指定解析器在解析注釋時(shí)執(zhí)行的回調(diào)函數(shù),一個(gè)函數(shù)。默認(rèn)為 null。
例如,以下代碼可以使用 sourceType
選項(xiàng)將代碼解析為 ES6 模塊:
const { parse } = require('@babel/parser');
const code = 'export const x = 1;';
const ast = parse(code, { sourceType: 'module' });
在這個(gè)例子中,我們使用 parse()
函數(shù)解析一個(gè) ES6 模塊,將 sourceType
選項(xiàng)指定為 "module"
。
@babel/traverse
@babel/traverse
是一個(gè)用于對抽象語法樹(AST)進(jìn)行遞歸遍歷和更新的工具庫,它可以通過訪問和修改 AST 節(jié)點(diǎn)來實(shí)現(xiàn)代碼轉(zhuǎn)換。
下面是一個(gè)簡單的示例代碼,其中包含了使用 @babel/parser
將 JavaScript 代碼解析為 AST,并使用 @babel/traverse
對 AST 進(jìn)行遍歷和更新的過程。
const parser = require('@babel/parser');
const generator= require('@babel/generator').default
const traverse = require('@babel/traverse').default;
// 將 JavaScript 代碼解析為 AST
const ast = parser.parse(`
const double = x => x * 2;
const result = double(3);
`);
// 遍歷 AST,并對所有函數(shù)調(diào)用進(jìn)行修改
traverse(ast, {
CallExpression(path) {
// 如果當(dāng)前節(jié)點(diǎn)表示一個(gè)函數(shù)調(diào)用
if (path.node.callee.name === 'double') {
// 將函數(shù)名修改為 triple
path.node.callee.name = 'triple';
// 將函數(shù)調(diào)用的參數(shù)修改為原參數(shù)的兩倍
path.node.arguments[0] = {
type: 'NumericLiteral',
value: path.node.arguments[0].value * 2,
};
}
},
});
// 將修改后的 AST 轉(zhuǎn)換回 JavaScript 代碼
const code =generator(ast).code;
console.log(code);
在這個(gè)示例代碼中,我們首先使用 @babel/parser
將 JavaScript 代碼解析為 AST,然后使用 @babel/traverse
對 AST 進(jìn)行遞歸遍歷,并對所有函數(shù)調(diào)用進(jìn)行修改。具體來說,我們將函數(shù) double
的名稱修改為 triple
,并將函數(shù)調(diào)用的參數(shù)修改為原參數(shù)的兩倍。最后,我們使用 @babel/generator
將修改后的 AST 轉(zhuǎn)換回 JavaScript
代碼,并輸出轉(zhuǎn)換后的代碼。
下面這個(gè)示例,它演示了如何使用 @babel/traverse 對 JavaScript 代碼進(jìn)行轉(zhuǎn)換,將 var 關(guān)鍵字替換成 const 關(guān)鍵字。
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generator = require('@babel/generator').default;
const code = `
var a = 1;
var b = 2;
var c = a + b;
`;
// 將 JavaScript 代碼解析為 AST
const ast = parser.parse(code);
// 遍歷 AST,并將所有 var 聲明替換成 const 聲明
traverse(ast, {
VariableDeclaration(path) {
if (path.node.kind === 'var') {
path.node.kind = 'const';
}
},
});
// 將修改后的 AST 轉(zhuǎn)換回 JavaScript 代碼
const output = generator(ast, {}, code);
console.log(output.code);
在這個(gè)示例中,我們首先將 JavaScript 代碼傳入 @babel/parser,
將其解析為 AST。然后我們使用 @babel/traverse
對 AST 進(jìn)行遍歷,遍歷過程中,我們判斷當(dāng)前節(jié)點(diǎn)是否為變量聲明節(jié)點(diǎn),如果是,則將節(jié)點(diǎn)中的 kind
屬性(即變量聲明的關(guān)鍵字)替換成 const
。最后,我們使用 @babel/generator
將修改后的 AST 轉(zhuǎn)換回 JavaScript 代碼,并輸出轉(zhuǎn)換后的代碼。
值得注意的是,@babel/traverse
遍歷 AST 時(shí),它會(huì)對每個(gè)節(jié)點(diǎn)調(diào)用與節(jié)點(diǎn)類型對應(yīng)的方法。例如,在這個(gè)示例代碼中,我們使用了 CallExpression
方法,因?yàn)槲覀円獙λ泻瘮?shù)調(diào)用進(jìn)行修改。如果你要對其他類型的節(jié)點(diǎn)進(jìn)行遍歷和修改,可以參考 @babel/traverse
的文檔,查看支持的節(jié)點(diǎn)類型和相應(yīng)的方法名。
path對象
在@babel/traverse
模塊中,path
對象表示一個(gè)節(jié)點(diǎn)(node)
在 AST 樹中的位置,提供了一些屬性和方法,用于訪問節(jié)點(diǎn)的屬性、子節(jié)點(diǎn)、父節(jié)點(diǎn)、兄弟節(jié)點(diǎn)等,并且可以對 AST 樹進(jìn)行修改。下面是 path 對象的一些常用屬性和方法的詳細(xì)說明:
-
path.node
node
是path
對象上的一個(gè)屬性,表示當(dāng)前節(jié)點(diǎn)的 AST 表示。node 對象包含了當(dāng)前節(jié)點(diǎn)的所有屬性和方法,你可以在鉤子函數(shù)中使用path.node
訪問和修改節(jié)點(diǎn)的屬性和方法。 -
例如,如果當(dāng)前節(jié)點(diǎn)是一個(gè)函數(shù)聲明(
FunctionDeclaration
),那么path.node.id
就是函數(shù)的名字,path.node.params
就是函數(shù)的參數(shù)列表,path.node.body
就是函數(shù)的主體等。 -
path.parent
parent
是path
對象上的一個(gè)屬性,表示當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)。你可以使用path.parent
訪問和修改當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn),也可以使用path.parentPath
來獲取父路徑對象。 -
path.scope
scope
是path
對象上的一個(gè)屬性,表示當(dāng)前節(jié)點(diǎn)的作用域。作用域?qū)ο蟀水?dāng)前節(jié)點(diǎn)的變量聲明和函數(shù)聲明等信息。你可以使用path.scope
訪問和修改當(dāng)前節(jié)點(diǎn)的作用域。
- path.get(key)
get()
是 path 對象上的一個(gè)方法,用于獲取當(dāng)前節(jié)點(diǎn)的指定子節(jié)點(diǎn)。你可以使用 path.get(key)
訪問當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn),其中 key 可以是一個(gè)屬性名、一個(gè)下標(biāo)或者一個(gè)函數(shù)。
-
例如,如果當(dāng)前節(jié)點(diǎn)是一個(gè)函數(shù)調(diào)用(
CallExpression
),那么path.get('callee')
就是獲取函數(shù)名節(jié)點(diǎn),path.get('arguments')[0]
就是獲取第一個(gè)參數(shù)節(jié)點(diǎn)。 -
path.traverse(visitor)
traverse()
是path
對象上的一個(gè)方法,用于遍歷當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn),并調(diào)用鉤子函數(shù)處理子節(jié)點(diǎn)。你可以使用path.traverse(visitor)
遍歷當(dāng)前節(jié)點(diǎn)的所有子節(jié)點(diǎn),其中visitor
是一個(gè)對象,包含了 enter 和 exit 兩個(gè)鉤子函數(shù)。 -
例如,如果當(dāng)前節(jié)點(diǎn)是一個(gè)
if
語句(IfStatement
),那么可以使用path.get('consequent').traverse(visitor)
遍歷 if 語句的主體部分,并且在遍歷每個(gè)子節(jié)點(diǎn)時(shí)調(diào)用enter
和exit
鉤子函數(shù)。 -
path.replaceWith(newNode)
replaceWith()
是 path 對象上的一個(gè)方法,用于替換當(dāng)前節(jié)點(diǎn)。你可以使用path.replaceWith(newNode)
來替換當(dāng)前節(jié)點(diǎn),其中 newNode 是一個(gè)新的節(jié)點(diǎn)。 -
例如,如果當(dāng)前節(jié)點(diǎn)是一個(gè)變量聲明(
VariableDeclaration
),可以使用path.replaceWith(t.expressionStatement(t.stringLiteral('new code')))
替換當(dāng)前節(jié)點(diǎn)為一個(gè)新的表達(dá)式語句。
- path.remove()
remove()
是 path 對象上的一個(gè)方法,用于刪除當(dāng)前節(jié)點(diǎn)。你可以使用 path.remove() 刪除當(dāng)前
scope屬性
在Babel中,@babel/traverse
模塊的path對象提供了一個(gè)scope
屬性,代表當(dāng)前路徑節(jié)點(diǎn)的作用域。在遍歷語法樹時(shí),可以使用scope
屬性來判斷當(dāng)前節(jié)點(diǎn)是否處于某個(gè)特定的作用域內(nèi),以及在這個(gè)作用域內(nèi)聲明的變量和函數(shù)。
scope
對象包含一些屬性和方法,用于表示當(dāng)前節(jié)點(diǎn)的作用域信息,比如:
-
block
:當(dāng)前節(jié)點(diǎn)所在的塊級作用域 -
path
:當(dāng)前節(jié)點(diǎn)對應(yīng)的路徑 -
bindings
:當(dāng)前作用域內(nèi)所有綁定(變量或函數(shù))的信息
其中,bindings
屬性是一個(gè)對象,它的鍵是所有在當(dāng)前作用域中定義的變量或函數(shù)名,值是綁定對象,代表這些變量或函數(shù)的定義信息,比如它們的類型、聲明的位置等。
bindings
對象的值也是一個(gè)對象,包含了以下屬性:
-
kind
:變量或函數(shù)的類型,可以是var、let、const、function等 -
path
:該變量或函數(shù)的定義路徑 -
constantViolations
:使用該變量或函數(shù)的非常量路徑 -
referencePaths
:使用該變量或函數(shù)的所有路徑 -
referenced
:是否被引用過 -
references
:引用的路徑數(shù)量
例如,下面的示例代碼演示了如何使用scope對象查找當(dāng)前作用域中所有的變量和函數(shù):
const babel = require('@babel/core');
const traverse = require('@babel/traverse').default;
const code = `
const a = 1;
function foo() {
const b = 2;
console.log(a, b);
}
foo();
`;
const ast = babel.parse(code);
traverse(ast, {
enter(path) {
const bindings = path.scope.bindings;
console.log('Node:', path.node.type, 'Bindings:', Object.keys(bindings));
}
});
在這個(gè)例子中,我們首先使用@babel/core
模塊的parse
方法將源代碼解析為AST。然后使用traverse
方法遍歷AST,在遍歷每個(gè)節(jié)點(diǎn)時(shí)打印出它的類型和作用域信息。
在這個(gè)例子中,我們可以看到,在作用域中定義了a變量和foo函數(shù),它們都被存儲(chǔ)在bindings
對象中,我們可以通過遍歷bindings
對象來獲取這些變量和函數(shù)的信息。
bindings對象
每個(gè) scope
對象都有一個(gè)名為 bindings
的屬性,它是一個(gè)包含當(dāng)前作用域中所有綁定的對象。在 JavaScript 中,綁定指的是標(biāo)識(shí)符(Identifier
)和值(Value
)之間的關(guān)系。bindings
對象提供了一種獲取在當(dāng)前作用域中綁定的標(biāo)識(shí)符和它們的值的方式。
bindings
對象是一個(gè)包含鍵值對的對象,其中鍵是標(biāo)識(shí)符名稱,值是描述綁定的對象。描述綁定的對象包含有關(guān)綁定的信息,例如綁定所在的節(jié)點(diǎn)、綁定的類型(變量、函數(shù)等)以及綁定的范圍。此外,描述綁定的對象還提供了許多有用的方法,例如獲取綁定的類型、獲取綁定的值等。
以下是一個(gè)示例代碼,展示如何使用 bindings
對象獲取當(dāng)前作用域中的所有綁定:
const babel = require('@babel/core');
const t = require('@babel/types');
const traverse = require('@babel/traverse').default;
const code = `
const foo = 1;
function bar() {}
console.log(foo + bar());
`;
const ast = babel.parseSync(code);
traverse(ast, {
Program(path) {
const { bindings } = path.scope;
for (const name in bindings) {
if (bindings.hasOwnProperty(name)) {
console.log(`Binding: ${name}`);
console.log(`Binding type: ${bindings[name].kind}`);
console.log(`Binding value: ${bindings[name].path.node}`);
}
}
},
});
在這個(gè)示例中,我們遍歷了 AST 并在 Program
節(jié)點(diǎn)上獲取了當(dāng)前作用域的 bindings
對象。然后,我們遍歷了 bindings
對象并打印了每個(gè)綁定的名稱、類型和值。注意,bindings[name].path.node
獲取到的是一個(gè) AST 節(jié)點(diǎn),如果想要獲取到節(jié)點(diǎn)對應(yīng)的值,需要使用 @babel/generator
模塊將其轉(zhuǎn)換為代碼。
const babel = require('@babel/core');
const t = require('@babel/types');
const traverse = require('@babel/traverse').default;
const code = `
const foo = 1;
function bar() {
console.log(foo);
}
`;
const ast = babel.parseSync(code);
traverse(ast, {
FunctionDeclaration(path) {
const { bindings } = path.scope;
for (const name in bindings) {
if (bindings.hasOwnProperty(name)) {
console.log(`Binding: ${name}`);
console.log(`Binding type: ${bindings[name].kind}`);
console.log(`Binding value: ${bindings[name].path.node}`);
// 獲取標(biāo)識(shí)符節(jié)點(diǎn)
const identifier = bindings[name].identifier;
console.log(`Identifier: ${identifier.name}`);
// 獲取綁定的所有引用
const references = bindings[name].referencePaths;
for (const reference of references) {
console.log(`Reference: ${reference.node}`);
}
}
}
},
});
在這個(gè)示例中,我們遍歷了 AST 并在FunctionDeclaration
節(jié)點(diǎn)上獲取了當(dāng)前作用域的 bindings 對象。然后,我們遍歷了 bindings
對象并打印了每個(gè)綁定的名稱、類型和值。接著,我們使用 bindings[name].identifier
獲取了綁定的標(biāo)識(shí)符節(jié)點(diǎn),并使用 bindings[name].referencePaths
獲取了綁定的所有引用。注意,bindings[name].referencePaths
獲取到的是一個(gè)路徑數(shù)組,每個(gè)路徑都包含一個(gè)引用節(jié)點(diǎn)。
@babel/generator
@babel/generator
是 Babel 中負(fù)責(zé)將 AST 轉(zhuǎn)換回 JavaScript 代碼的模塊。它提供了一個(gè) default
方法,該方法接受一個(gè) AST 作為參數(shù),并返回轉(zhuǎn)換后的 JavaScript 代碼。
下面是一個(gè)簡單的示例,演示了如何使用 @babel/generator
將 AST 轉(zhuǎn)換回 JavaScript
代碼:
const generator = require('@babel/generator').default;
const ast = {
type: 'Program',
body: [
{
type: 'VariableDeclaration',
kind: 'const',
declarations: [
{
type: 'VariableDeclarator',
id: { type: 'Identifier', name: 'x' },
init: { type: 'NumericLiteral', value: 42 },
},
],
},
],
};
const code = generator(ast, {}, '');
console.log(code.code); // 輸出:const x = 42;
在這個(gè)示例中,我們首先定義了一個(gè) AST,它表示了一段簡單的代碼,其中包含一個(gè) const
聲明語句。然后,我們使用 @babel/generator
的 default
方法將 AST 轉(zhuǎn)換回 JavaScript
代碼。由于我們不需要傳遞任何配置項(xiàng),所以第二個(gè)參數(shù)傳入一個(gè)空對象。最后,我們輸出轉(zhuǎn)換后的代碼。
除了 default
方法外,@babel/generator
還提供了一些其他方法,例如 generate
方法,它與 default
方法功能類似,但可以在不同的選項(xiàng)和插件配置下生成代碼。此外,還有 CodeGenerator
類,它可以在多個(gè) AST 轉(zhuǎn)換之間共享狀態(tài),以提高性能。
總之,@babel/generator
是一個(gè)非常有用的模塊,它讓你可以輕松地將 AST 轉(zhuǎn)換回 JavaScript
代碼,為你的代碼轉(zhuǎn)換工作提供了很大的便利。
@babel/types
@babel/types
模塊提供了一組用于操作 AST 的工具,包括 AST 節(jié)點(diǎn)類型的定義
、節(jié)點(diǎn)創(chuàng)建
、節(jié)點(diǎn)判斷
等功能。下面簡單介紹一下 @babel/types
模塊中的常用功能。
節(jié)點(diǎn)類型
@babel/types
模塊定義了 ECMAScript 代碼中的各種 AST 節(jié)點(diǎn)類型,例如 Identifier
、BinaryExpression
、BlockStatement
等。每個(gè)節(jié)點(diǎn)類型都是一個(gè) JavaScript 對象,包含該節(jié)點(diǎn)類型的屬性和方法。例如,Identifier
節(jié)點(diǎn)類型包含以下屬性:
-
type
:節(jié)點(diǎn)類型,固定為 “Identifier”。 -
name
:標(biāo)識(shí)符的名稱,一個(gè)字符串。 -
start
:標(biāo)識(shí)符在源代碼中的起始位置(行號(hào)和列號(hào))。 -
end
:標(biāo)識(shí)符在源代碼中的結(jié)束位置(行號(hào)和列號(hào))。 -
loc
:標(biāo)識(shí)符在源代碼中的位置信息,包含 start 和 end 兩個(gè)屬性。
在使用 @babel/types
模塊時(shí),我們可以使用節(jié)點(diǎn)類型來創(chuàng)建新的節(jié)點(diǎn)或操作現(xiàn)有的節(jié)點(diǎn)。
節(jié)點(diǎn)創(chuàng)建
@babel/types
模塊提供了一組用于創(chuàng)建 AST 節(jié)點(diǎn)的工具函數(shù),例如 t.identifier(name)
、t.binaryExpression(operator, left, right)
等。這些函數(shù)會(huì)返回對應(yīng)的節(jié)點(diǎn)對象,我們可以將它們用于構(gòu)建新的 AST。
例如,以下代碼可以創(chuàng)建一個(gè) VariableDeclaration
節(jié)點(diǎn):
const t = require("@babel/types");
const variable = t.variableDeclaration(
'const',
[t.variableDeclarator(t.identifier('x'), t.numericLiteral(1))]
);
在這個(gè)例子中,我們使用 t.variableDeclaration
函數(shù)創(chuàng)建一個(gè) VariableDeclaration
節(jié)點(diǎn),其中指定了變量聲明的類型為 "const"
,變量名為 "x"
,初始值為 1
。
節(jié)點(diǎn)判斷
@babel/types
模塊還提供了一組用于判斷 AST 節(jié)點(diǎn)類型的工具函數(shù),例如 t.isIdentifier(node)
、t.isBinaryExpression(node)
等。這些函數(shù)會(huì)返回一個(gè)布爾值,用于判斷指定的節(jié)點(diǎn)對象是否屬于某個(gè)節(jié)點(diǎn)類型。
例如,以下代碼可以判斷一個(gè)節(jié)點(diǎn)是否為 CallExpression
類型:
const t = require("@babel/types");
function isCallExpression(node) {
return t.isCallExpression(node);
}
在這個(gè)例子中,我們定義了一個(gè)名為 isCallExpression
的函數(shù),它接受一個(gè)節(jié)點(diǎn)對象作為參數(shù),使用 t.isCallExpression
函數(shù)判斷該節(jié)點(diǎn)是否為 CallExpression
類型,并返回對應(yīng)的布爾值。
綜上所述,@babel/types
模塊提供了一組用于操作 AST 的工具函數(shù),可以用于創(chuàng)建、修改和判斷 AST 節(jié)點(diǎn)。我們可以使用它們來實(shí)現(xiàn)對 ECMAScript 代碼的轉(zhuǎn)換、分析和生成。文章來源:http://www.zghlxwxcb.cn/news/detail-472123.html
js代碼常見混淆方式
還原前
if (function (_0x48fd11, _0x52974e, _0x388e32) {
function _0x549dad(_0x3d08e6, _0x563a9f, _0x32d68f, _0x935a1e, _0x2f5432, _0x11c4c1) {
_0x563a9f = _0x563a9f >> 0x8,
_0x2f5432 = 'po';
var _0x868417 = 'shift',
_0xca0acf = 'push',
_0x11c4c1 = '?';
if (_0x563a9f < _0x3d08e6) {
while (--_0x3d08e6) {
_0x935a1e = _0x48fd11[_0x868417]();
if (_0x563a9f === _0x3d08e6 && _0x11c4c1 === '?' && _0x11c4c1['length'] === 0x1) {
_0x563a9f = _0x935a1e,
_0x32d68f = _0x48fd11[_0x2f5432 + 'p']();
} else if (_0x563a9f && _0x32d68f['replace'](/[UOMPNTnblILJdPG=]/g, '') === _0x563a9f) {
_0x48fd11[_0xca0acf](_0x935a1e);
}
}
_0x48fd11[_0xca0acf](_0x48fd11[_0x868417]());
}
return 0xf71c7;
};
function _0x4f91(_0x162067, _0x2c1c19) {
_0x162067 = ~~'0x'['concat'](_0x162067['slice'](0x1));
var _0x1a4d27 = _0x235f[_0x162067];
if (_0x4f91['MRYmFi'] === undefined) {
(function () {
var _0x27f542 = typeof window !== 'undefined' ? window : typeof process === 'object' && typeof require === 'function' && typeof global === 'object' ? global : this;
var _0x5773e8 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
_0x27f542['atob'] || (_0x27f542['atob'] = function (_0x1405c4) {
var _0x4321b2 = String(_0x1405c4)['replace'](/=+$/, '');
for (var _0x6467c8 = 0x0, _0x11d027, _0x40e1d1, _0x4cecb2 = 0x0, _0x2fff1b = ''; _0x40e1d1 = _0x4321b2['charAt'](_0x4cecb2++); ~_0x40e1d1 && (_0x11d027 = _0x6467c8 % 0x4 ? _0x11d027 * 0x40 + _0x40e1d1 : _0x40e1d1, _0x6467c8++ % 0x4) ? _0x2fff1b += String['fromCharCode'](0xff & _0x11d027 >> (-0x2 * _0x6467c8 & 0x6)) : 0x0) {
_0x40e1d1 = _0x5773e8['indexOf'](_0x40e1d1);
}
return _0x2fff1b;
});
}
());
function _0x5734d9(_0x57c809, _0x2c1c19) {
var _0xef41ee = [],
_0x110b95 = 0x0,
_0x244795,
_0x1bed23 = '',
_0x4a92ec = '';
_0x57c809 = atob(_0x57c809);
for (var _0x2253d1 = 0x0, _0x212cb4 = _0x57c809['length']; _0x2253d1 < _0x212cb4; _0x2253d1++) {
_0x4a92ec += '%' + ('00' + _0x57c809['charCodeAt'](_0x2253d1)['toString'](0x10))['slice'](-0x2);
}
_0x57c809 = decodeURIComponent(_0x4a92ec);
for (var _0xb19e69 = 0x0; _0xb19e69 < 0x100; _0xb19e69++) {
_0xef41ee[_0xb19e69] = _0xb19e69;
}
for (_0xb19e69 = 0x0; _0xb19e69 < 0x100; _0xb19e69++) {
_0x110b95 = (_0x110b95 + _0xef41ee[_0xb19e69] + _0x2c1c19['charCodeAt'](_0xb19e69 % _0x2c1c19['length'])) % 0x100;
_0x244795 = _0xef41ee[_0xb19e69];
_0xef41ee[_0xb19e69] = _0xef41ee[_0x110b95];
_0xef41ee[_0x110b95] = _0x244795;
}
_0xb19e69 = 0x0;
_0x110b95 = 0x0;
for (var _0x19db4a = 0x0; _0x19db4a < _0x57c809['length']; _0x19db4a++) {
_0xb19e69 = (_0xb19e69 + 0x1) % 0x100;
_0x110b95 = (_0x110b95 + _0xef41ee[_0xb19e69]) % 0x100;
_0x244795 = _0xef41ee[_0xb19e69];
_0xef41ee[_0xb19e69] = _0xef41ee[_0x110b95];
_0xef41ee[_0x110b95] = _0x244795;
_0x1bed23 += String['fromCharCode'](_0x57c809['charCodeAt'](_0x19db4a) ^ _0xef41ee[(_0xef41ee[_0xb19e69] + _0xef41ee[_0x110b95]) % 0x100]);
}
return _0x1bed23;
}
_0x4f91['FommKf'] = _0x5734d9;
_0x4f91['WVWLXP'] = {};
_0x4f91['MRYmFi'] = !![];
}
var _0x41c863 = _0x4f91['WVWLXP'][_0x162067];
if (_0x41c863 === undefined) {
if (_0x4f91['zUDdVD'] === undefined) {
var _0x7cb49b = function (_0x43d3b2) {
this['dBIiIv'] = _0x43d3b2;
this['ZJxFPE'] = [0x1, 0x0, 0x0];
this['DtEyxk'] = function () {
return 'newState';
};
this['StAszO'] = '\x5cw+\x20*\x5c(\x5c)\x20*{\x5cw+\x20*';
this['kCvLOW'] = '[\x27|\x22].+[\x27|\x22];?\x20*}';
};
_0x7cb49b['prototype']['MAUJkU'] = function () {
var _0x1bf2d5 = new RegExp(this['StAszO'] + this['kCvLOW']);
var _0x2af6bd = _0x1bf2d5['test'](this['DtEyxk']['toString']()) ? --this['ZJxFPE'][0x1] : --this['ZJxFPE'][0x0];
return this['VQGnUA'](_0x2af6bd);
};
_0x7cb49b['prototype']['VQGnUA'] = function (_0x2eb9c4) {
if (!Boolean(~_0x2eb9c4)) {
return _0x2eb9c4;
}
return this['YQnufA'](this['dBIiIv']);
};
_0x7cb49b['prototype']['YQnufA'] = function (_0x35a98e) {
for (var _0x49c03f = 0x0, _0x367fd6 = this['ZJxFPE']['length']; _0x49c03f < _0x367fd6; _0x49c03f++) {
this['ZJxFPE']['push'](Math['round'](Math['random']()));
_0x367fd6 = this['ZJxFPE']['length'];
}
return _0x35a98e(this['ZJxFPE'][0x0]);
};
new _0x7cb49b(_0x4f91)['MAUJkU']();
_0x4f91['zUDdVD'] = !![];
}
_0x1a4d27 = _0x4f91['FommKf'](_0x1a4d27, _0x2c1c19);
_0x4f91['WVWLXP'][_0x162067] = _0x1a4d27;
} else {
_0x1a4d27 = _0x41c863;
}
return _0x1a4d27;
};
還原后
-----------------------------------非全部代碼------------------------------------------------------------------
文章來源地址http://www.zghlxwxcb.cn/news/detail-472123.html
function getUrl(code, _0x3379ed, _0x46bf59) {
var _0x524375 = null;
var tp = new Date()["getTime"]();
var key = CryptoJS["enc"]["Utf8"]["parse"](md5(pid + "-" + tp)["substring"](0x0, 0x10));
var encryptedData = CryptoJS["AES"]["encrypt"](pid + "-" + tp, key, {
"mode": CryptoJS["mode"]["ECB"],
"padding": CryptoJS["pad"]["Pkcs7"]
});
$["ajax"]("/god/" + pid, {
"async": _0x3379ed,
"method": "POST",
"dataType": "json",
"data": {
"t": tp,
"sg": base64ToHex(encryptedData + ""),
"verifyCode": code
},
"success": function (result) {
if (result["url"] != null) {
_0x524375 = dealUrl(result);
_0x120e92 = _0x524375;
lines["push"](_0x524375);
_0x46bf59(result["url"]);
} else {
window["dp"]["notice"](result["error"], 0xbb8);
}
}
});
return _0x524375;
}
還原插件
- 去除十六進(jìn)制編碼
function decodeHexString(ast) {
traverse(ast, {
StringLiteral(path) {
path.get('extra').remove();
}
})
code = generator(ast).code;
ast= parser.parse(code);
return ast;
}
- 刪除未被引用變量
function removeUnusedVariables(ast){
const visitor ={
VariableDeclarator(path) {
const {id} = path.node;
const binding = path.scope.getBinding(id.name);
//如果變量被修改過,則不能進(jìn)行刪除動(dòng)作。
if (!binding || binding.constantViolations.length > 0) {
return;
}
//長度為0,說明變量沒有被使用過。
if (binding.referencePaths.length === 0) {
path.remove();
}
},
}
traverse(ast,visitor);
code = generator(ast).code;
ast= parser.parse(code);
return ast;
}
- 流程平坦化switch語句
function decodeSwitch(ast){
for(let i = 0; i < 100; i++){
traverse(ast, {
MemberExpression(path) {
if (t.isStringLiteral(path.node.object) &&
t.isStringLiteral(path.node.property, {
value: 'split'
})) {
//找到類型為 VariableDeclaration 的父節(jié)點(diǎn)
let varPath = path.findParent(function (p) {
return t.isVariableDeclaration(p);
});
//獲取下一個(gè)同級節(jié)點(diǎn)
let whilePath = varPath.getSibling(varPath.key + 1);
//解析整個(gè) switch
let myArr = [];
whilePath.node.body.body[0].cases.map(function (p) {
myArr[p.test.value] = p.consequent[0];
});
let parentPath = whilePath.parent;
varPath.remove();
whilePath.remove();
// path.node.object.value 取到的是 '1|2|4|7|5|3|8|0|6'
let shufferArr = path.node.object.value.split("|");
shufferArr.map(function (v) {
parentPath.body.push(myArr[v]);
});
path.stop();
}
}
});
}
code = generator(ast).code;
ast= parser.parse(code);
return ast;
}
- object 還原
function decodeObject(ast){
const visitor ={
VariableDeclarator(path) {
const {id, init} = path.node;
//特征判斷,對象為空則不處理
if (!t.isObjectExpression(init) || init.properties.length === 0) return;
let name = id.name;
let scope = path.scope;
for (const property of init.properties) {//遍歷key、value
let key = property.key.value;
let value = property.value;
//一般ob混淆,key長度都是5,也有是3的,自行調(diào)整即可。
if (key.length !== 5) return;
//如果是字面量
if (t.isLiteral(value)) {
scope.traverse(scope.block, {
//遍歷MemberExpression,找出與key相同的表達(dá)式
MemberExpression(_path) {
let _node = _path.node;
if (!t.isIdentifier(_node.object, {name: name})) return;
if (!t.isLiteral(_node.property, {value: key})) return;
_path.replaceWith(value);
},
})
}
//如果是函數(shù)表達(dá)式
else if (t.isFunctionExpression(value)) {
let ret_state = value.body.body[0];
//特征判斷,如果不是return表達(dá)式
if (!t.isReturnStatement(ret_state)) continue;
scope.traverse(scope.block, {
CallExpression: function (_path) {
//遍歷CallExpression
let {callee, arguments} = _path.node;
if (!t.isMemberExpression(callee)) return;
if (!t.isIdentifier(callee.object, {name: name})) return;
if (!t.isLiteral(callee.property, {value: key})) return;
if (t.isCallExpression(ret_state.argument) && arguments.length > 0) {
//構(gòu)造節(jié)點(diǎn)
_path.replaceWith(t.CallExpression(arguments[0], arguments.slice(1)));
} else if (t.isBinaryExpression(ret_state.argument) && arguments.length === 2) {
//構(gòu)造節(jié)點(diǎn)
let replace_node = t.BinaryExpression(ret_state.argument.operator, arguments[0], arguments[1]);
_path.replaceWith(replace_node);
} else if (t.isLogicalExpression(ret_state.argument) && arguments.length === 2) {
//構(gòu)造節(jié)點(diǎn)
let replace_node = t.LogicalExpression(ret_state.argument.operator, arguments[0], arguments[1]);
_path.replaceWith(replace_node);
}
}
})
}
}
},
}
traverse(ast,visitor);
code = generator(ast).code;
ast= parser.parse(code);
return ast;
}
到了這里,關(guān)于深入學(xué)習(xí) JavaScript 轉(zhuǎn)譯器 Babel ,AST還原混淆代碼的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!