我试图用许多短函数来构建我的代码,因此由于函数和变量的流动,即使没有很多注释,它也很容易阅读。去年,我们用 C 实现了一个高性能应用程序,其结构相同,但为了避免函数调用,我们将几乎每个函数都注释为内联。
现在我正在用 CoffeeScript 编写一个 NodeJS 应用程序,并用数十个从循环调用的小函数来构建我的部分代码。这将聚合到几百到几千个函数调用,具体取决于数据结构的大小。由于这些函数可以合并为一个更大的函数,因此可以避免调用,但这会大大降低代码的质量。
我在 SO 上找到了对 Extreme JavaScript Performance 的引用,这是 2009 年的演示,显示了在 Firefox 和 IE 上内联函数时的巨大性能改进,以及 Chrome 和 Safari 的显着改进。现在已经是 5 年前了,JS 引擎随着时间的推移而不断改进,所以我的问题是:
当 CoffeeScript 创建 JS 或由解析器在 JS 中使用某些特定关键字或参数时,是否可以强制内联函数?
或者我应该等待 V8 引擎自动实现并处理这个问题(或者可能已经完成了)?
我已经编写了一个javascript来使用esprima+escodegen进行内联,它仅适用于简单的情况: 示例:
const code = `
function f(x) {
return x**2 - 1;
}
export function f1(x) {
return f(x + 1);
}`;
console.log(inlineFunctionsWrapper(code));
结果:
export function f1(x) {
const $x = x + 1;
return $x ** 2 - 1;
}
剧本:
import esprima from 'esprima';
import escodegen from 'escodegen';
const functionsToInline = new Map();
function collectFunctions(node) {
if (node.type === 'FunctionDeclaration') {
const name = node.id.name;
functionsToInline.set(name, node);
} else if (node.type === 'ClassDeclaration') {
const className = node.id.name;
if (node.body.type !== 'ClassBody') {
for (const member of node.body.body) {
if (member.type === 'MethodDefinition') {
const name = member.key.name;
const fullName = className + '.' + name;
functionsToInline.set(fullName, member.value);
}
}
}
} else {
for (const key in node) {
const x = node[key];
if (typeof x === 'object' && x != null) {
collectFunctions(x);
}
}
}
}
function renameIdentifiers(map, node) {
if (node.type === 'Identifier') {
const newNode = map.get(node.name);
if (newNode != undefined) {
return newNode;
}
} else {
for (const key in node) {
const x = node[key];
if (typeof x === 'object' && x != null) {
node[key] = renameIdentifiers(map, x);
}
}
}
return node;
}
function collectIdentifiers(set, node) {
if (node.type === 'VariableDeclarator') {
set.add(node.id.name);
}
for (const key in node) {
const x = node[key];
if (typeof x === 'object' && x != null) {
collectIdentifiers(set, x);
}
}
return node;
}
function clone(x) {
return JSON.parse(JSON.stringify(x));
}
function createVariableDeclaration(name, initNode, constant) {
return {
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": name
},
"init": initNode
}
],
"kind": constant ? "const" : "let"
};
}
const tmps = new Set();
function getTmpVarName(baseName) {
if (baseName.startsWith('$')) {
baseName = baseName.slice(1).replace(/\d+$/g, '');
}
let tmpVarName = '$' + baseName;
let i = 1;
while (tmps.has(tmpVarName)) {
tmpVarName = '$' + baseName + '' + i;
i += 1;
}
tmps.add(tmpVarName);
return tmpVarName;
}
function contains(container, node) {
if (node === container) {
return true;
}
for (const key in container) {
const x = container[key];
if (typeof x === 'object' && x != null) {
if (contains(x, node)) {
return true;
}
}
}
return false;
}
function has(node, type) {
if (node.type === type) {
return true;
}
for (const key in node) {
const x = node[key];
if (typeof x === 'object' && x != null) {
if (has(x, type)) {
return true;
}
}
}
return false;
}
function isConstant(node, name) {
if (node.type === 'AssignmentExpression' && node.left.type === 'Identifier' && node.left.name === name) {
return false;
}
if (node.type === 'UpdateExpression' && node.argument.type === 'Identifier' && node.argument.name === name) {
return false;
}
for (const key in node) {
const x = node[key];
if (typeof x === 'object' && x != null) {
if (!isConstant(x, name)) {
return false;
}
}
}
return true;
}
function inlineFunctions(block, node, exported) {
if (node.type === 'CallExpression' && (node.callee.type === 'MemberExpression' || node.callee.type === 'Identifier')) {
const name = node.callee.type === 'MemberExpression' ? node.callee.object.name + '.' + node.callee.property.name : node.callee.name;
const f = functionsToInline.get(name);
if (f != null) {
const paramMap = new Map();
for (let i = 0; i < f.params.length; i += 1) {
let argNode = node.arguments[i];
const param = f.params[i];
if ((argNode.type !== 'Identifier' || !isConstant(f, param.name)) &&
(argNode.type !== 'Literal' || !isConstant(f, param.name))) {
argNode = inlineFunctions(block, argNode, exported);
let index = 0;
while (!contains(block.body[index], node)) {
index += 1;
}
const tmpVarName = getTmpVarName(param.name);
block.body.splice(index, 0, createVariableDeclaration(tmpVarName, argNode, isConstant(f, param.name)));
argNode = {
type: 'Identifier',
name: tmpVarName
};
}
if (param.type !== 'Identifier') {
throw new Error();
}
const name = param.name;
paramMap.set(name, argNode);
}
// rename internal variables:
const vars = new Set();
collectIdentifiers(vars, f.body);
for (const variable of vars) {
const newName = getTmpVarName(variable);
paramMap.set(variable, {
type: 'Identifier',
name: newName
});
}
let index = 0;
while (!contains(block.body[index], node)) {
index += 1;
}
let valid = true;
if (has(block.body[index], 'AssignmentExpression')) {
if (block.body[index].type === 'ExpressionStatement' && block.body[index].expression.type === 'AssignmentExpression') {
if (has(block.body[index].expression.left, 'AssignmentExpression')) {
valid = false;
}
if (has(block.body[index].expression.right, 'AssignmentExpression')) {
valid = false;
}
} else {
valid = false;
}
}
if (!valid) {
throw new RangeError('not supported when there is an AssignmentExpression: ' + JSON.stringify(block.body[index]));
}
if (has(block.body[index], 'UpdateExpression')) {
throw new RangeError('not supported when there is an UpdateExpression: ' + JSON.stringify(block.body[index]));
}
const parentNode = block.body[index];
if (contains(parentNode, node)) {
if (f.body.body[f.body.body.length - 1].type === 'ReturnStatement') {
for (let i = 0; i < f.body.body.length - 1; i += 1) {
if (has(f.body.body[i], 'ReturnStatement')) {
throw new RangeError('not supported when there is extra ReturnStatement');
}
block.body.splice(index + i, 0, renameIdentifiers(paramMap, clone(f.body.body[i])));
}
return renameIdentifiers(paramMap, clone(f.body.body[f.body.body.length - 1].argument));
} else {
throw new Error('return statement should be the last statement');
}
}
console.log(JSON.stringify(block.body, null, 2));
console.log(JSON.stringify(node, null, 2));
throw new Error(parentNode.type);
}
}
if (node.type === 'ExportNamedDeclaration' && !exported) {
return inlineFunctions(block, node, true);
}
if (node.type === 'BlockStatement') {
for (let i = 0; i < node.body.length; i += 1) {
const x = node.body[i];
if (typeof x === 'object') {
inlineFunctions(node, x, exported);
}
}
return node;
}
for (const key in node) {
const x = node[key];
if (typeof x === 'object' && x != null) {
node[key] = inlineFunctions(block, x, exported);
}
}
if (node.type === 'FunctionDeclaration') {
if (!exported) {
// remove inlined function
return {
type: "EmptyStatement"
};
}
}
return node;
}
function inlineFunctionsWrapper(code, whiteList = []) {
let tree = esprima.parse(code, { sourceType: 'module'});
collectFunctions(tree);
for (let name of whiteList.values()) {
functionsToInline.delete(name);
}
tree = inlineFunctions(null, tree, false);
return escodegen.generate(tree);
}
export default inlineFunctionsWrapper;