JavaScript Function Declarations vs. Function Expressions

本文探讨了JavaScript中两种函数定义方式:函数声明与函数表达式,并解释了它们之间的主要区别,尤其是提升(hoisting)行为的不同表现。

原创转载请注明出处:http://agilestyle.iteye.com/blog/2341737

 

There are actually two literal forms of functions.

The first is a function declaration, which begins with the function keyword and includes the name of the function immediately following it. The contents of the function are enclosed in braces, as shown in this declaration:

function add(num1, num2) {
    return num1 + num2;
}

 

The second form is a function expression, which doesn't require a name after function. These functions are considered anonymous because the function object itself has no name. Instead, function expressions are typically referenced via a variable or property, as in this expression:

var add = function(num1, num2) {
    return num1 + num2;
};

 

Although these two forms are quite similar, they differ in a very important way. Function declarations are hoisted to the top of the context (either the function in which the declaration occurs or the global scope) when the code is executed. That means you can actually define a function after it is used in code without generating an error. For example:

var result = add(5, 5);

function add(num1, num2) {
    return num1 + num2;
}

console.log(result);    // 10

 

This code might look like it will cause an error, but it works just fine. That's because the JavaScript engine hoists the function declaration to the top and actually executes the code as if it were written like this:

function add(num1, num2) {
    return num1 + num2;
}

var result = add(5, 5);

console.log(result);    // 10

 

Function hoisting happens only for function declarations because the function name is known ahead of time. Function expressions, on the other hand, cannot be hoisted because the functions can be referenced only through a variable. So this code causes an error:

// error!
var result = add(5, 5);
var add = function (num1, num2) {
    return num1 + num2;
};

 

 

Reference

Leanpub.Principles.of.Object-Oriented.Programming.in.JavaScript.Jun.2014 

 

 

const fs = require('fs'); const path = require('path'); const parser = require('@babel/parser'); const traverse = require('@babel/traverse').default; const t = require('@babel/types'); const generator = require('@babel/generator').default; const vm = require('vm'); // Helper to check if a node is a literal function isLiteral(node) { return t.isStringLiteral(node) || t.isNumericLiteral(node) || t.isBooleanLiteral(node) || t.isNullLiteral(node); } async function main() { const inputFile = path.join(__dirname, 'example-1.js'); if (!fs.existsSync(inputFile)) { console.error(`File ${inputFile} not found.`); return; } const code = fs.readFileSync(inputFile, 'utf8'); console.log(`Processing ${inputFile} (${code.length} bytes)...`); const ast = parser.parse(code, { sourceType: 'script', plugins: ['bigInt'] }); // --------------------------------------------------------- // Step 1: Prepare Execution Environment (Prelude & Full Execution) // --------------------------------------------------------- let mainIIFEPath = null; let mainBlock = null; let iifeScope = null; traverse(ast, { CallExpression(path) { if (t.isFunctionExpression(path.node.callee) && !mainIIFEPath) { const body = path.node.callee.body.body; if (body.length > 50) { mainIIFEPath = path; mainBlock = body; iifeScope = path.get('callee').scope; path.stop(); } } } }); if (!mainIIFEPath) { console.error("Could not find main IIFE."); return; } console.log("Found main IIFE."); // Identify IIFE variables and functions const iifeVars = new Set(); mainBlock.forEach(node => { if (t.isVariableDeclaration(node)) { node.declarations.forEach(dec => { if (t.isIdentifier(dec.id)) { iifeVars.add(dec.id.name); } }); } else if (t.isFunctionDeclaration(node)) { if (node.id && t.isIdentifier(node.id)) { iifeVars.add(node.id.name); } } }); // Add potentially missing critical variables ['tC', 'Rl', 'Wg', 'MS', 'Um', 'kg', 'hW', 'JQ', 'qp', 'KC', 'lQ', 'l2', 'b6', 'hO', 'An', 'H8', 'Cc', 'bL', 'Y8', 'Un', 'ZO', 'DH', 'd7', 'cY', 'XO', 'A7', 'QS', 'Mp', 'TO', 'Vg', 'k6', 'Vx', 'OC', 'kH', 'N8', 'pH', 'vm', 'Qc', 'IO', 'Qm', 'nm', 'CX', 'wc', 'XB', 'sS', 'Up', 'Bf', 'Fb', 'zw', 'lC', 'IX', 'Zc', 'Xl', 'jl', 'cH', 'T4', 'pv', 'tY', 'A8', 'rL', 'lF', 'B8', 'YO', 'Mq', 'K6', 'XKN', 'AQ', 'SL', 'zH', 'hd', 'n7', 'QQ', 'Hr', 'Ih', 'fm', 'pB', 'md', 'm4', 'ptN', 'cQ', 'ZhN', 'cw', 'krN', 'Vp', 'Dc', 'kt', 'ZS', 'pp', 'X8', 'El'].forEach(v => iifeVars.add(v)); // Setup VM Sandbox with enhanced mocks const sandbox = { window: { screen: { colorDepth: 24, pixelDepth: 24, availWidth: 1920, availHeight: 1080 }, navigator: { userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', plugins: [], maxTouchPoints: 0, languages: ['en-US', 'en'], hardwareConcurrency: 4, deviceMemory: 8, webdriver: false, onLine: true, cookieEnabled: true, javaEnabled: () => false, doNotTrack: null, platform: 'Win32', appName: 'Netscape', appCodeName: 'Mozilla', product: 'Gecko', vendor: 'Google Inc.' }, document: { createElement: () => ({ style: {}, appendChild: () => {}, remove: () => {}, getAttribute: () => null, setAttribute: () => {} }), location: { protocol: 'https:', hostname: 'example.com', href: 'https://example.com/' }, addEventListener: () => {}, attachEvent: () => {}, documentElement: { style: {} }, head: { appendChild: () => {} }, body: { appendChild: () => {} }, cookie: '', referrer: '', getElementById: () => null, getElementsByTagName: () => [] }, chrome: { runtime: {} }, performance: { memory: { jsHeapSizeLimit: 2147483648, totalJSHeapSize: 10000000, usedJSHeapSize: 5000000 }, now: () => Date.now() }, localStorage: { getItem: () => null, setItem: () => {}, removeItem: () => {} }, sessionStorage: { getItem: () => null, setItem: () => {}, removeItem: () => {} }, indexedDB: {}, openDatabase: () => {}, DeviceOrientationEvent: {}, DeviceMotionEvent: {}, TouchEvent: {}, history: {}, location: { protocol: 'https:', hostname: 'example.com', href: 'https://example.com/' }, getComputedStyle: () => ({}), frames: { ServiceWorkerRegistration: {} }, ServiceWorker: {}, ServiceWorkerContainer: {}, innerWidth: 1920, innerHeight: 1080, outerWidth: 1920, outerHeight: 1080, screenX: 0, screenY: 0, pageXOffset: 0, pageYOffset: 0, top: {}, parent: {}, self: {} }, document: { createElement: () => ({ style: {}, appendChild: () => {}, remove: () => {}, getAttribute: () => null, setAttribute: () => {} }), location: { protocol: 'https:', hostname: 'example.com' }, documentElement: { style: {} }, head: { appendChild: () => {} }, body: { appendChild: () => {} }, referrer: '' }, navigator: { userAgent: 'Mozilla/5.0' }, console: console, Array: Array, String: String, Object: Object, Math: Math, parseInt: parseInt, parseFloat: parseFloat, RegExp: RegExp, Date: Date, Function: Function, JSON: JSON, Error: Error, TypeError: TypeError, setTimeout: (fn) => fn(), setInterval: () => {}, clearTimeout: () => {}, clearInterval: () => {}, encodeURIComponent: encodeURIComponent, decodeURIComponent: decodeURIComponent, unescape: unescape, escape: escape }; // Circular references and polyfills sandbox.window.window = sandbox.window; sandbox.window.self = sandbox.window; sandbox.window.global = sandbox.window; sandbox.window.top = sandbox.window; sandbox.window.parent = sandbox.window; // Copy standard globals to window ['Array', 'String', 'Object', 'Math', 'parseInt', 'parseFloat', 'RegExp', 'Date', 'Function', 'JSON', 'Error', 'TypeError', 'encodeURIComponent', 'decodeURIComponent', 'unescape', 'escape', 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval'].forEach(prop => { sandbox.window[prop] = sandbox[prop]; }); sandbox.self = sandbox.window; sandbox.global = sandbox.window; const vmCtx = vm.createContext(sandbox); // Instrument IIFE to export variables at the end using Try-Catch-Finally // Split into functions (export early) and variables (export late) const funcNames = new Set(); const varNames = new Set(); mainBlock.forEach(node => { if (t.isFunctionDeclaration(node) && node.id && t.isIdentifier(node.id)) { funcNames.add(node.id.name); } else if (t.isVariableDeclaration(node)) { node.declarations.forEach(dec => { if (t.isIdentifier(dec.id)) { varNames.add(dec.id.name); } }); } }); // Manual list classification (best effort) const manualFuncs = ['ZhN', 'cw', 'krN', 'Vp', 'Dc', 'kt', 'ZS', 'pp', 'X8', 'El', 'fw', 'wf', 'j8', 'v3N', 'k3N', 'LM', 'FPN', 'P6', 'Zc', 'l2', 'G0N']; const manualVars = ['tC', 'Rl', 'Wg', 'MS', 'Um', 'kg', 'hW', 'JQ', 'qp', 'KC', 'lQ', 'b6', 'hO', 'An', 'H8', 'Cc', 'bL', 'Y8', 'Un', 'ZO', 'DH', 'd7', 'cY', 'XO', 'A7', 'QS', 'Mp', 'TO', 'Vg', 'k6', 'Vx', 'OC', 'kH', 'N8', 'pH', 'vm', 'Qc', 'IO', 'Qm', 'nm', 'CX', 'wc', 'XB', 'sS', 'Up', 'Bf', 'Fb', 'zw', 'lC', 'IX', 'Xl', 'jl', 'cH', 'T4', 'pv', 'tY', 'A8', 'rL', 'lF', 'B8', 'YO', 'Mq', 'K6', 'XKN', 'AQ', 'SL', 'zH', 'hd', 'n7', 'QQ', 'Hr', 'Ih', 'fm', 'pB', 'md', 'm4', 'ptN', 'cQ']; manualFuncs.forEach(f => funcNames.add(f)); manualVars.forEach(v => varNames.add(v)); const funcExportCode = Array.from(funcNames).map(v => `try { window['${v}'] = ${v}; } catch(e) {}`).join('\n'); const varExportCode = Array.from(varNames).map(v => `try { window['${v}'] = ${v}; } catch(e) {}`).join('\n'); const originalBody = [...mainBlock]; mainBlock.length = 0; // Clear // Prepend function exports to try block const funcExportNodes = parser.parse(funcExportCode).program.body; const tryBodyNodes = [...funcExportNodes, ...originalBody]; const tryBlock = t.blockStatement(tryBodyNodes); const catchBlock = t.catchClause(t.identifier('e'), t.blockStatement([ t.expressionStatement(t.callExpression( t.memberExpression(t.identifier('console'), t.identifier('log')), [t.stringLiteral("Error in IIFE execution:"), t.memberExpression(t.identifier('e'), t.identifier('message'))] )) ])); const finalizerBlock = t.blockStatement(parser.parse(varExportCode).program.body); const tryStmt = t.tryStatement(tryBlock, catchBlock, finalizerBlock); mainBlock.push(tryStmt); // Re-generate the code from AST const instrumentedCode = generator(ast).code; console.log("Executing instrumented IIFE in VM..."); try { vm.runInContext(instrumentedCode, vmCtx, { timeout: 10000 }); console.log("IIFE execution completed (possibly with caught errors)."); } catch (e) { console.error("Error executing IIFE wrapper:", e.message); } function evaluateInVM(codeSnippet) { try { return vm.runInContext(codeSnippet, vmCtx); } catch (e) { return undefined; } } // --------------------------------------------------------- // Step 2: Constant Propagation (Aggressive & Assignment Aware) // --------------------------------------------------------- console.log("Starting constant propagation..."); let constantCount = 0; // 1. Track assignments to all top-level variables const assignmentCounts = new Map(); const invalidCandidates = new Set(); traverse(ast, { AssignmentExpression(path) { if (t.isIdentifier(path.node.left)) { const name = path.node.left.name; assignmentCounts.set(name, (assignmentCounts.get(name) || 0) + 1); } }, UpdateExpression(path) { if (t.isIdentifier(path.node.argument)) { const name = path.node.argument.name; invalidCandidates.add(name); } }, VariableDeclarator(path) { if (t.isIdentifier(path.node.id) && path.node.init) { const name = path.node.id.name; assignmentCounts.set(name, (assignmentCounts.get(name) || 0) + 1); } } }); // 2. Identify constants from VM state const constantBindings = new Map(); // binding -> value for (const key of iifeVars) { if (invalidCandidates.has(key)) continue; const count = assignmentCounts.get(key) || 0; // Allow 1 assignment (init) or 0 (if implicit/VM magic, though unlikely) if (count <= 1) { try { const val = sandbox.window[key]; if (val !== undefined && (typeof val === 'number' || typeof val === 'boolean' || typeof val === 'string')) { const binding = iifeScope.getBinding(key); if (binding) { constantBindings.set(binding, val); } } } catch (e) {} } } console.log(`Identified ${constantBindings.size} global constants (single-assignment).`); // 3. Propagate traverse(ast, { Identifier(path) { // Don't replace declarations or assignments if (path.key === 'id' || path.key === 'params' || path.key === 'property' && !path.parent.computed) return; if (t.isAssignmentExpression(path.parent) && path.parent.left === path.node) return; if (t.isUpdateExpression(path.parent)) return; const binding = path.scope.getBinding(path.node.name); if (binding && constantBindings.has(binding)) { path.replaceWith(t.valueToNode(constantBindings.get(binding))); constantCount++; } } }); console.log(`Propagated ${constantCount} constants.`); // --------------------------------------------------------- // Step 3: Proxy Replacement & String Decoding // --------------------------------------------------------- console.log("Starting proxy replacement and string decoding..."); let replacedCount = 0; // Helper to statically extract array from function function extractArrayFromFunction(funcName, funcNode) { // Case 1: function f() { return ['a', 'b']; } if (funcNode.body.body.length === 1 && t.isReturnStatement(funcNode.body.body[0])) { const arg = funcNode.body.body[0].argument; if (t.isArrayExpression(arg)) { return arg.elements.map(e => t.isLiteral(e) ? e.value : null); } } // Case 2: function f() { var x = ['a']; f = function() { return x; }; return x; } // Akamai style: // function ZhN() { // var wXN = ['XA', ...]; // ZhN = function() { return wXN; }; // return wXN; // } const body = funcNode.body.body; for (const stmt of body) { if (t.isVariableDeclaration(stmt)) { for (const dec of stmt.declarations) { if (t.isArrayExpression(dec.init)) { return dec.init.elements.map(e => t.isLiteral(e) ? e.value : null); } } } } return null; } // Detect array-based string decoders const stringArrays = new Map(); // Debug: Check if ZhN is available try { console.log("Type of ZhN in VM:", evaluateInVM("typeof ZhN")); console.log("Type of cw in VM:", evaluateInVM("typeof cw")); } catch(e) { console.log("Debug check failed:", e.message); } mainBlock.forEach(node => { if (t.isFunctionDeclaration(node) && node.id) { const funcName = node.id.name; let result = null; // Try static extraction first (safer/faster) const staticArray = extractArrayFromFunction(funcName, node); if (staticArray && staticArray.length > 10 && typeof staticArray[0] === 'string') { console.log(`Statically detected string array function: ${funcName} (${staticArray.length} strings)`); result = staticArray; } // Try VM if static failed if (!result) { try { const vmResult = evaluateInVM(`${funcName}()`); if (Array.isArray(vmResult) && vmResult.length > 10 && typeof vmResult[0] === 'string') { console.log(`VM detected string array function: ${funcName} (${vmResult.length} strings)`); result = vmResult; } } catch (e) { // console.log(`Failed to evaluate ${funcName}: ${e.message}`); } } if (result) { stringArrays.set(funcName, result); } } }); const stringHelpers = new Map(); mainBlock.forEach(node => { if (t.isFunctionDeclaration(node) && node.id) { const body = node.body.body; if (body.length === 1 && t.isReturnStatement(body[0])) { const arg = body[0].argument; if (t.isMemberExpression(arg) && t.isCallExpression(arg.object) && t.isIdentifier(arg.object.callee)) { const arrayFuncName = arg.object.callee.name; if (stringArrays.has(arrayFuncName)) { console.log(`Detected string helper: ${node.id.name} -> ${arrayFuncName}`); stringHelpers.set(node.id.name, { arrayFunc: arrayFuncName, array: stringArrays.get(arrayFuncName) }); } } } } }); traverse(ast, { CallExpression(path) { if (isLiteral(path.node)) return; if (t.isIdentifier(path.node.callee) && stringHelpers.has(path.node.callee.name)) { const helper = stringHelpers.get(path.node.callee.name); if (path.node.arguments.length === 1 && isLiteral(path.node.arguments[0])) { let index = -1; if (t.isNumericLiteral(path.node.arguments[0])) index = path.node.arguments[0].value; if (index >= 0 && index < helper.array.length) { path.replaceWith(t.valueToNode(helper.array[index])); replacedCount++; return; } } } // General VM evaluation for other proxies (now that constants are propagated) try { const codeSnippet = generator(path.node).code; if (codeSnippet.length > 100) return; const allSimple = path.node.arguments.every(a => isLiteral(a) || t.isIdentifier(a)); if (!allSimple) return; const ids = codeSnippet.match(/[a-zA-Z_$][a-zA-Z0-9_$]*/g) || []; const allGlobal = ids.every(id => sandbox.window.hasOwnProperty(id) || sandbox.hasOwnProperty(id) || global[id] !== undefined); if (allGlobal) { const result = evaluateInVM(codeSnippet); if (result !== undefined && (typeof result === 'string' || typeof result === 'number' || typeof result === 'boolean')) { path.replaceWith(t.valueToNode(result)); replacedCount++; } } } catch (e) {} }, MemberExpression(path) { if (t.isCallExpression(path.node.object) && t.isIdentifier(path.node.object.callee)) { const funcName = path.node.object.callee.name; if (stringArrays.has(funcName)) { const arr = stringArrays.get(funcName); let index = -1; if (t.isNumericLiteral(path.node.property)) { index = path.node.property.value; } else if (path.node.computed) { // Now that constants are propagated, this might resolve try { const propCode = generator(path.node.property).code; const val = evaluateInVM(propCode); if (typeof val === 'number') index = val; } catch(e) {} } if (index >= 0 && index < arr.length) { path.replaceWith(t.valueToNode(arr[index])); replacedCount++; } } } } }); console.log(`Replaced ${replacedCount} proxy calls/strings.`); // --------------------------------------------------------- // Step 4: Control Flow Unflattening // --------------------------------------------------------- let unflattenedCount = 0; const flattenedFunctions = new Map(); traverse(ast, { Function(path) { if (path.isFunctionDeclaration() || path.isFunctionExpression()) { const info = analyzeFlattenedFunction(path); if (info) { if (path.node.id) flattenedFunctions.set(path.node.id.name, info); if (path.parentPath.isVariableDeclarator()) { flattenedFunctions.set(path.parentPath.node.id.name, info); } } } } }); function analyzeFlattenedFunction(path) { const body = path.get('body'); if (!body.isBlockStatement()) return null; const statements = body.get('body'); let loopStmt = statements.find(p => p.isWhileStatement() || p.isForStatement()); if (!loopStmt) return null; let loopBody = loopStmt.get('body'); if (!loopBody.isBlockStatement()) return null; let switchStmt = loopBody.get('body').find(p => p.isSwitchStatement()); if (!switchStmt) return null; const discriminant = switchStmt.node.discriminant; if (!t.isIdentifier(discriminant)) return null; const stateVarName = discriminant.name; const cases = new Map(); switchStmt.get('cases').forEach(casePath => { const test = casePath.node.test; if (test) { if (t.isLiteral(test)) { cases.set(test.value, casePath); } else { try { const val = evaluateInVM(generator(test).code); if (val !== undefined) cases.set(val, casePath); } catch(e) {} } } }); const params = path.node.params; let entryArgIndex = -1; params.forEach((param, index) => { if (t.isIdentifier(param) && param.name === stateVarName) entryArgIndex = index; }); return { path, stateVarName, cases, entryArgIndex }; } traverse(ast, { CallExpression(path) { const callee = path.node.callee; let funcName = null; if (t.isIdentifier(callee)) funcName = callee.name; if (funcName && flattenedFunctions.has(funcName)) { const info = flattenedFunctions.get(funcName); if (info.entryArgIndex === -1) return; const startStateArg = path.node.arguments[info.entryArgIndex]; if (!startStateArg) return; let startStateVal; if (isLiteral(startStateArg)) startStateVal = startStateArg.value; else { try { startStateVal = evaluateInVM(generator(startStateArg).code); } catch(e) {} } if (startStateVal !== undefined) { console.log(`Unflattening call to ${funcName} with state ${startStateVal}`); const newBody = reconstructPath(info, startStateVal, path.node.arguments); if (newBody && newBody.length > 0) { const iife = t.callExpression( t.functionExpression(null, [], t.blockStatement(newBody)), [] ); t.inheritsComments(iife, path.node); path.replaceWith(iife); unflattenedCount++; } } } } }); function reconstructPath(info, startState, callArgs) { const { stateVarName, cases } = info; let currentState = startState; const nodes = []; const visited = new Set(); let iterations = 0; const MAX_ITERATIONS = 5000; while (iterations < MAX_ITERATIONS) { if (!cases.has(currentState)) break; if (visited.has(currentState)) break; visited.add(currentState); const casePath = cases.get(currentState); const caseBody = casePath.node.consequent; let nextState = null; let hasReturn = false; for (const stmt of caseBody) { if (t.isBreakStatement(stmt)) continue; if (t.isExpressionStatement(stmt) && t.isAssignmentExpression(stmt.expression)) { const left = stmt.expression.left; if (t.isIdentifier(left) && left.name === stateVarName) { try { const operator = stmt.expression.operator; const rightCode = generator(stmt.expression.right).code; const rightVal = evaluateInVM(rightCode); if (rightVal !== undefined) { if (operator === '=') nextState = rightVal; else if (operator === '+=') nextState = currentState + rightVal; else if (operator === '-=') nextState = currentState - rightVal; } else { nextState = null; } } catch (e) {} continue; } } if (t.isReturnStatement(stmt)) hasReturn = true; nodes.push(t.cloneNode(stmt)); } if (hasReturn) break; if (nextState === null) break; currentState = nextState; iterations++; } return nodes; } console.log(`Unflattened ${unflattenedCount} calls.`); // --------------------------------------------------------- // Step 5: Function Inlining // --------------------------------------------------------- console.log("Starting function inlining..."); let inlinedCount = 0; const inlineableFunctions = new Map(); traverse(ast, { VariableDeclarator(path) { if (t.isIdentifier(path.node.id) && t.isFunctionExpression(path.node.init)) { if (isInlineable(path.node.init)) inlineableFunctions.set(path.node.id.name, path.node.init); } }, FunctionDeclaration(path) { if (t.isIdentifier(path.node.id)) { if (isInlineable(path.node)) inlineableFunctions.set(path.node.id.name, path.node); } } }); function isInlineable(node) { const body = node.body.body; if (body.length !== 1) return false; return t.isReturnStatement(body[0]); } traverse(ast, { CallExpression(path) { const callee = path.node.callee; if (t.isIdentifier(callee) && inlineableFunctions.has(callee.name)) { const funcNode = inlineableFunctions.get(callee.name); const returnArg = funcNode.body.body[0].argument; const params = funcNode.params; const args = path.node.arguments; if (args.length >= params.length) { const substitutions = new Map(); params.forEach((param, index) => { if (t.isIdentifier(param)) substitutions.set(param.name, args[index]); }); const safeReplace = (node) => { if (!node) return node; if (t.isIdentifier(node) && substitutions.has(node.name)) return t.cloneNode(substitutions.get(node.name)); if (t.isBinaryExpression(node) || t.isLogicalExpression(node)) { node.left = safeReplace(node.left); node.right = safeReplace(node.right); return node; } if (t.isUnaryExpression(node)) { node.argument = safeReplace(node.argument); return node; } if (t.isCallExpression(node)) { node.callee = safeReplace(node.callee); node.arguments = node.arguments.map(safeReplace); return node; } if (t.isMemberExpression(node)) { node.object = safeReplace(node.object); if (node.computed) node.property = safeReplace(node.property); return node; } return node; }; path.replaceWith(safeReplace(t.cloneNode(returnArg))); inlinedCount++; } } } }); console.log(`Inlined ${inlinedCount} function calls.`); // --------------------------------------------------------- // Step 6: Constant Folding // --------------------------------------------------------- console.log("Starting constant folding..."); let foldedCount = 0; traverse(ast, { BinaryExpression(path) { if (isLiteral(path.node.left) && isLiteral(path.node.right)) { try { const val = evaluateInVM(generator(path.node).code); if (val !== undefined && (typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean')) { path.replaceWith(t.valueToNode(val)); foldedCount++; } } catch(e) {} } }, ConditionalExpression(path) { if (isLiteral(path.node.test)) { if (path.node.test.value) path.replaceWith(path.node.consequent); else path.replaceWith(path.node.alternate); foldedCount++; } } }); console.log(`Folded ${foldedCount} expressions.`); // --------------------------------------------------------- // Step 7: Dead Code Elimination // --------------------------------------------------------- console.log("Starting dead code elimination..."); let deadCount = 0; traverse(ast, { Program(path) { path.scope.crawl(); } }); traverse(ast, { VariableDeclarator(path) { const binding = path.scope.getBinding(path.node.id.name); if (binding && !binding.referenced && binding.constant) { if (isPure(path.node.init)) { path.remove(); deadCount++; } } } }); function isPure(node) { if (!node) return true; if (isLiteral(node) || t.isIdentifier(node) || t.isFunction(node)) return true; return false; } console.log(`Removed ${deadCount} unused variables.`); // Output const outputOpts = { compact: false, comments: true }; const output = generator(ast, outputOpts).code; fs.writeFileSync(path.join(__dirname, 'example-1-deobfuscated.js'), output); console.log("Saved to example-1-deobfuscated.js"); } main().catch(err => console.error(err)); what next ?
11-24
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值