optimise named functions depending on whether they contain an assignment/reference - #861

This commit is contained in:
Sebastian McKenzie
2015-02-22 21:35:08 +11:00
parent ab6bb35a4f
commit f2d60aab9e
15 changed files with 225 additions and 187 deletions

View File

@@ -15,11 +15,32 @@ var visitor = {
if (localDeclar !== state.outerDeclar) return;
state.selfReference = true;
this.stop();
state.references.push(this);
if (t.isPattern(parent) || t.isAssignmentExpression(parent) || t.isUpdateExpression(parent)) {
state.selfAssignment = true;
}
}
};
var wrap = function (method, id, scope) {
var wrapIncludesSelfReference = function (state, method, id, scope) {
var outerId = scope.generateUidIdentifier("getOuter");
var outerIdCall = t.callExpression(outerId, []);
for (var i = 0; i < state.references.length; i++) {
state.references[i].replaceNode(outerIdCall);
}
method.id = id;
return util.template("named-function", {
GET_OUTER_ID: outerId,
FUNCTION_ID: id,
FUNCTION: method
});
};
var wrapIncludesSelfAssignment = function (state, method, id, scope) {
var templateName = "property-method-assignment-wrapper";
if (method.generator) templateName += "-generator";
return util.template(templateName, {
@@ -30,23 +51,68 @@ var wrap = function (method, id, scope) {
});
};
var wrap = function (state, method, id, scope) {
if (state.selfReference) {
if (state.selfAssignment) {
return wrapIncludesSelfAssignment(state, method, id, scope);
} else {
return wrapIncludesSelfReference(state, method, id, scope);
}
} else {
method.id = id;
return method;
}
};
var visit = function (node, name, scope) {
var state = {
name: name,
selfReference: false,
outerDeclar: scope.getBindingIdentifier(name),
selfAssignment: false,
selfReference: false,
outerDeclar: scope.getBindingIdentifier(name),
references: [],
name: name,
};
if (doesntHaveLocal(name, scope)) {
// check to see if we have a local binding of the id we're setting inside of
// the function, this is important as there are caveats associated
var bindingInfo = scope.getOwnBindingInfo(name);
if (bindingInfo) {
if (bindingInfo.kind === "param") {
// safari will blow up in strict mode with code like:
//
// var t = function t(t) {};
//
// with the error:
//
// Cannot declare a parameter named 't' as it shadows the name of a
// strict mode function.
//
// this isn't to the spec and they've invented this behaviour which is
// **extremely** annoying so we avoid setting the name if it has a param
// with the same id
state.selfAssignment = true;
state.selfReference = true;
} else {
// otherwise it's defined somewhere in scope like:
//
// var t = function () {
// var t = 2;
// };
//
// so we can safely just set the id and move along as it shadows the
// bound function id
}
} else {
scope.traverse(node, visitor, state);
}
return state;
};
var doesntHaveLocal = function (name, scope) {
var bindingInfo = scope.getOwnBindingInfo(name);
return !bindingInfo || bindingInfo.kind !== "param";
var hasParamOfSameName = function (name, scope) {
return bindingInfo && bindingInfo.kind === "param";
};
exports.property = function (node, file, scope) {
@@ -58,12 +124,7 @@ exports.property = function (node, file, scope) {
var method = node.value;
var state = visit(method, name, scope);
if (state.selfReference) {
node.value = wrap(method, id, scope);
} else {
method.id = id;
}
node.value = wrap(state, method, id, scope);
};
exports.bare = function (node, parent, scope) {
@@ -86,13 +147,6 @@ exports.bare = function (node, parent, scope) {
var name = t.toIdentifier(id.name);
id = t.identifier(name);
//
var state = visit(node, name, scope);
if (state.selfReference) {
return wrap(node, id, scope);
} else {
node.id = id;
}
return wrap(state, node, id, scope);
};

View File

@@ -1,7 +0,0 @@
(function () {
function GET_OUTER_ID() {
return ID;
}
return FUNCTION;
})()

View File

@@ -0,0 +1,7 @@
(function () {
function GET_OUTER_ID() {
return FUNCTION_ID;
}
return FUNCTION;
})()