Optimize _.template when no evaluate delimiters are used and optimize _.bind when partially applied in V8.

Former-commit-id: 25489d41ba3cac7ac3f1414e09f1971a11a779be
This commit is contained in:
John-David Dalton
2012-07-02 00:07:22 -04:00
parent 6af4652161
commit 3b4074bfc7
3 changed files with 113 additions and 73 deletions

View File

@@ -301,7 +301,7 @@
* @returns {String} Returns the formatted source. * @returns {String} Returns the formatted source.
*/ */
function getFunctionSource(func) { function getFunctionSource(func) {
var source = (func + ''); var source = func.source || (func + '');
return source.replace(/\n(?:.*)/g, function(match, index) { return source.replace(/\n(?:.*)/g, function(match, index) {
match = match.slice(1); match = match.slice(1);
return ( return (
@@ -465,19 +465,6 @@
return removeFromCreateIterator(source, varName); return removeFromCreateIterator(source, varName);
} }
/**
* Removes non-syntax critical whitespace from a string.
*
* @private
* @param {String} source The source to process.
* @returns {String} Returns the source with whitespace removed.
*/
function removeWhitespace(source) {
return source.replace(/\[object |else if|function | in |return\s+[\w']|throw |typeof |var |@ |\\\\n|\\n|\s+/g, function(match) {
return match == false || match == '\\n' ? '' : match;
});
}
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
// Backbone build // Backbone build
@@ -720,8 +707,8 @@
} }
else { else {
// inline `iteratorTemplate` template // inline `iteratorTemplate` template
source = source.replace(/(( +)var iteratorTemplate *= *)([\s\S]+?\n\2.+?);\n/, (function() { source = source.replace(/(( +)var iteratorTemplate *= *)[\s\S]+?\n\2.+?;\n/, (function() {
var code = getFunctionSource(lodash._iteratorTemplate.source); var code = getFunctionSource(lodash._iteratorTemplate);
// expand properties to avoid having to use a with-statement // expand properties to avoid having to use a with-statement
iteratorOptions.forEach(function(property) { iteratorOptions.forEach(function(property) {
@@ -735,7 +722,6 @@
.replace(/__p *\+= *' *';/g, '') .replace(/__p *\+= *' *';/g, '')
.replace(/(__p *\+= *)' *' *\+/g, '$1') .replace(/(__p *\+= *)' *' *\+/g, '$1')
.replace(/\+\s*' *';/g, ';') .replace(/\+\s*' *';/g, ';')
.replace(/';(?:\\n|\s)*} *'/g, "'}'")
.replace(/(\{) *;|; *(\})/g, '$1$2') .replace(/(\{) *;|; *(\})/g, '$1$2')
.replace(/\(\(__t *= *\( *([^)]+) *\)\) *== *null *\? *'' *: *__t\)/g, '$1'); .replace(/\(\(__t *= *\( *([^)]+) *\)\) *== *null *\? *'' *: *__t\)/g, '$1');

View File

@@ -207,7 +207,10 @@
source = source.replace(RegExp('\\.(' + propWhitelist.join('|') + ')\\b', 'g'), "['$1']"); source = source.replace(RegExp('\\.(' + propWhitelist.join('|') + ')\\b', 'g'), "['$1']");
// remove brackets from `_.escape()` in `tokenizeEscape` // remove brackets from `_.escape()` in `tokenizeEscape`
source = source.replace("_['escape'](\"", '_.escape("'); source = source.replace(/_\['escape']\("/, '_.escape("');
// remove brackets from `_.escape()` in `_.template`
source = source.replace(/__e *= *_\['escape']/, '__e=_.escape');
// remove brackets from `result[length].value` in `_.sortBy` // remove brackets from `result[length].value` in `_.sortBy`
source = source.replace("result[length]['value']", 'result[length].value'); source = source.replace("result[length]['value']", 'result[length].value');
@@ -220,6 +223,11 @@
}); });
}); });
// remove whitespace from `_.template` related regexpes
source = source.replace(/(?:reEmptyString\w+|reInsertVariable) *=.+/g, function(match) {
return match.replace(/ /g, '');
});
// remove newline from double-quoted string in `_.template` // remove newline from double-quoted string in `_.template`
source = source.replace('"\';\\n"', '"\';"'); source = source.replace('"\';\\n"', '"\';"');

156
lodash.js
View File

@@ -8,10 +8,21 @@
;(function(window, undefined) { ;(function(window, undefined) {
'use strict'; 'use strict';
/**
* Used to match potentially incorrect data object references, i.e. `obj.obj`,
* in compiled templates. The variables are assigned in `_.template`.
*/
var lastVariable,
reDoubleVariable;
/** Detect free variable `exports` */ /** Detect free variable `exports` */
var freeExports = typeof exports == 'object' && exports && var freeExports = typeof exports == 'object' && exports &&
(typeof global == 'object' && global && global == global.global && (window = global), exports); (typeof global == 'object' && global && global == global.global && (window = global), exports);
/** Native prototype shortcuts */
var ArrayProto = Array.prototype,
ObjectProto = Object.prototype;
/** /**
* Detect the JScript [[DontEnum]] bug: * Detect the JScript [[DontEnum]] bug:
* In IE < 9 an objects own properties, shadowing non-enumerable ones, are * In IE < 9 an objects own properties, shadowing non-enumerable ones, are
@@ -25,10 +36,20 @@
/** Used to restore the original `_` reference in `noConflict` */ /** Used to restore the original `_` reference in `noConflict` */
var oldDash = window._; var oldDash = window._;
/** Used to match empty strings in compiled templates */
var reEmptyStringEvaluate = /\b__p \+= '';/g,
reEmptyStringInterpolate = /\b__p \+= '' \+/g,
reEmptyStringHybrid = /\b__t\) \+\n'';/g;
/** Used to insert the data object variable into compiled templates */
var reInsertVariable = /(?:__e|__t = )\(\s*(?![\s"']|this\.)/g;
/** Used to detect if a method is native */ /** Used to detect if a method is native */
var reNative = RegExp('^' + ({}.valueOf + '') var reNative = RegExp('^' +
.replace(/[.*+?^=!:${}()|[\]\/\\]/g, '\\$&') (ObjectProto.valueOf + '')
.replace(/valueOf|for [^\]]+/g, '.+?') + '$'); .replace(/[.*+?^=!:${}()|[\]\/\\]/g, '\\$&')
.replace(/valueOf|for [^\]]+/g, '.+?') + '$'
);
/** Used to match tokens in template text */ /** Used to match tokens in template text */
var reToken = /__token__(\d+)/g; var reToken = /__token__(\d+)/g;
@@ -54,12 +75,41 @@
/** Used to store tokenized template text snippets */ /** Used to store tokenized template text snippets */
var tokenized = []; var tokenized = [];
/* Detect if `Function#bind` exists and is inferred to be fast (i.e. all but V8) */
var useNativeBind = nativeBind && /\n|Opera/.test(nativeBind + toString.call(window.opera));
/** Detect if sourceURL syntax is usable without erroring */ /** Detect if sourceURL syntax is usable without erroring */
try { try {
// Adobe's and Narwhal's JS engines will error // Adobe's and Narwhal's JS engines will error
var useSourceURL = (Function('//@')(), true); var useSourceURL = (Function('//@')(), true);
} catch(e){ } } catch(e){ }
/** Native method shortcuts */
var concat = ArrayProto.concat,
hasOwnProperty = ObjectProto.hasOwnProperty,
push = ArrayProto.push,
slice = ArrayProto.slice,
toString = ObjectProto.toString;
/* Native method shortcuts for methods with the same name as other `lodash` methods */
var nativeBind = reNative.test(nativeBind = slice.bind) && nativeBind,
nativeIsArray = reNative.test(nativeIsArray = Array.isArray) && nativeIsArray,
nativeIsFinite = window.isFinite,
nativeKeys = reNative.test(nativeKeys = Object.keys) && nativeKeys;
/** Object#toString result shortcuts */
var arrayClass = '[object Array]',
boolClass = '[object Boolean]',
dateClass = '[object Date]',
funcClass = '[object Function]',
numberClass = '[object Number]',
regexpClass = '[object RegExp]',
stringClass = '[object String]';
/** Timer shortcuts */
var clearTimeout = window.clearTimeout,
setTimeout = window.setTimeout;
/** /**
* Used to escape characters for inclusion in HTML. * Used to escape characters for inclusion in HTML.
* The `>` and `/` characters don't require escaping in HTML and have no * The `>` and `/` characters don't require escaping in HTML and have no
@@ -94,39 +144,6 @@
'\u2029': 'u2029' '\u2029': 'u2029'
}; };
/** Object#toString result shortcuts */
var arrayClass = '[object Array]',
boolClass = '[object Boolean]',
dateClass = '[object Date]',
funcClass = '[object Function]',
numberClass = '[object Number]',
regexpClass = '[object RegExp]',
stringClass = '[object String]';
/** Native prototype shortcuts */
var ArrayProto = Array.prototype,
ObjectProto = Object.prototype;
/** Native method shortcuts */
var concat = ArrayProto.concat,
hasOwnProperty = ObjectProto.hasOwnProperty,
push = ArrayProto.push,
slice = ArrayProto.slice,
toString = ObjectProto.toString;
/* Used if `Function#bind` exists and is inferred to be fast (i.e. all but V8) */
var nativeBind = reNative.test(nativeBind = slice.bind) &&
/\n|Opera/.test(nativeBind + toString.call(window.opera)) && nativeBind;
/* Native method shortcuts for methods with the same name as other `lodash` methods */
var nativeIsArray = reNative.test(nativeIsArray = Array.isArray) && nativeIsArray,
nativeIsFinite = window.isFinite,
nativeKeys = reNative.test(nativeKeys = Object.keys) && nativeKeys;
/** Timer shortcuts */
var clearTimeout = window.clearTimeout,
setTimeout = window.setTimeout;
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
/** /**
@@ -158,8 +175,8 @@
} }
/** /**
* By default, Lo-Dash uses ERB-style template delimiters, change the * By default, Lo-Dash uses embedded Ruby (ERB) style template delimiters,
* following template settings to use alternative delimiters. * change the following template settings to use alternative delimiters.
* *
* @static * @static
* @memberOf _ * @memberOf _
@@ -227,7 +244,7 @@
' <% if (objectBranch) { %>\nif (length === length >>> 0) {<% } %>\n' + ' <% if (objectBranch) { %>\nif (length === length >>> 0) {<% } %>\n' +
' <%= arrayBranch.beforeLoop %>;\n' + ' <%= arrayBranch.beforeLoop %>;\n' +
' while (<%= arrayBranch.loopExp %>) {\n' + ' while (<%= arrayBranch.loopExp %>) {\n' +
' <%= arrayBranch.inLoop %>;\n' + ' <%= arrayBranch.inLoop %>\n' +
' }' + ' }' +
' <% if (objectBranch) { %>\n}\n<% }' + ' <% if (objectBranch) { %>\n}\n<% }' +
'}' + '}' +
@@ -254,7 +271,7 @@
// the the `prototype` property of functions regardless of its // the the `prototype` property of functions regardless of its
// [[Enumerable]] value. // [[Enumerable]] value.
' if (!(skipProto && index == \'prototype\')<% if (useHas) { %> && <%= hasExp %><% } %>) {\n' + ' if (!(skipProto && index == \'prototype\')<% if (useHas) { %> && <%= hasExp %><% } %>) {\n' +
' <%= objectBranch.inLoop %>;\n' + ' <%= objectBranch.inLoop %>\n' +
' }' + ' }' +
' <% } %>\n' + ' <% } %>\n' +
' }' + ' }' +
@@ -271,7 +288,7 @@
' if (shadowed[k] == \'constructor\') {' + ' if (shadowed[k] == \'constructor\') {' +
' %>!(ctor && ctor.prototype === <%= iteratedObject %>) && <%' + ' %>!(ctor && ctor.prototype === <%= iteratedObject %>) && <%' +
' } %><%= hasExp %>) {\n' + ' } %><%= hasExp %>) {\n' +
' <%= objectBranch.inLoop %>;\n' + ' <%= objectBranch.inLoop %>\n' +
' }<%' + ' }<%' +
' }' + ' }' +
' }' + ' }' +
@@ -340,7 +357,7 @@
} }
}; };
/** Reusable iterator options for `invoke`, `map`, `pluck`, and `sortBy` */ /** Reusable iterator options for `invoke`, `map`, `pluck`, and `sortBy` */
var mapIteratorOptions = { var mapIteratorOptions = {
'init': '', 'init': '',
'exit': 'if (!collection) return []', 'exit': 'if (!collection) return []',
@@ -568,7 +585,7 @@
*/ */
function tokenizeEscape(match, value) { function tokenizeEscape(match, value) {
var index = tokenized.length; var index = tokenized.length;
tokenized[index] = "'+\n_.escape(" + value + ") +\n'"; tokenized[index] = "' +\n__e(" + value + ") +\n'";
return token + index; return token + index;
} }
@@ -582,7 +599,7 @@
*/ */
function tokenizeInterpolate(match, value) { function tokenizeInterpolate(match, value) {
var index = tokenized.length; var index = tokenized.length;
tokenized[index] = "'+\n((__t = (" + value + ")) == null ? '' : __t) +\n'"; tokenized[index] = "' +\n((__t = (" + value + ")) == null ? '' : __t) +\n'";
return token + index; return token + index;
} }
@@ -1859,24 +1876,24 @@
* *
* // basic bind * // basic bind
* var func = function(greeting) { * var func = function(greeting) {
* return greeting + ': ' + this.name; * return greeting + ' ' + this.name;
* }; * };
* *
* func = _.bind(func, { 'name': 'moe' }, 'hi'); * func = _.bind(func, { 'name': 'moe' }, 'hi');
* func(); * func();
* // => 'hi: moe' * // => 'hi moe'
* *
* // lazy bind * // lazy bind
* var object = { * var object = {
* 'name': 'moe', * 'name': 'moe',
* 'greet': function(greeting) { * 'greet': function(greeting) {
* return greeting + ': ' + this.name; * return greeting + ' ' + this.name;
* } * }
* }; * };
* *
* var func = _.bind(object, 'greet', 'hi'); * var func = _.bind(object, 'greet', 'hi');
* func(); * func();
* // => 'hi: moe' * // => 'hi moe'
* *
* object.greet = function(greeting) { * object.greet = function(greeting) {
* return greeting + ', ' + this.name + '!'; * return greeting + ', ' + this.name + '!';
@@ -1894,8 +1911,8 @@
methodName = thisArg; methodName = thisArg;
thisArg = func; thisArg = func;
} }
// use if `Function#bind` is faster // use if native `Function#bind` is faster
else if (nativeBind) { else if (useNativeBind || (nativeBind && arguments.length > 2)) {
return nativeBind.call.apply(nativeBind, arguments); return nativeBind.call.apply(nativeBind, arguments);
} }
@@ -1929,7 +1946,6 @@
} }
return func.apply(thisBinding, args); return func.apply(thisBinding, args);
} }
return bound; return bound;
} }
@@ -3185,7 +3201,8 @@
// https://github.com/olado/doT // https://github.com/olado/doT
options || (options = {}); options || (options = {});
var isEvaluating, var isEscaping,
isEvaluating,
isInterpolating, isInterpolating,
result, result,
defaults = lodash.templateSettings, defaults = lodash.templateSettings,
@@ -3207,7 +3224,7 @@
// tokenize delimiters to avoid escaping them // tokenize delimiters to avoid escaping them
if (escapeDelimiter) { if (escapeDelimiter) {
text = text.replace(escapeDelimiter, tokenizeEscape); isEscaping = text != (text = text.replace(escapeDelimiter, tokenizeEscape));
} }
if (interpolateDelimiter) { if (interpolateDelimiter) {
isInterpolating = text != (text = text.replace(interpolateDelimiter, tokenizeInterpolate)); isInterpolating = text != (text = text.replace(interpolateDelimiter, tokenizeInterpolate));
@@ -3225,10 +3242,35 @@
// clear stored code snippets // clear stored code snippets
tokenized.length = 0; tokenized.length = 0;
// if `options.variable` is not specified, add `data` to the top of the scope chain // strip concating empty strings
if (isInterpolating) {
text = text.replace(reEmptyStringInterpolate, '__p \+=');
}
if (isEvaluating) {
text = text.replace(reEmptyStringEvaluate, '');
if (isInterpolating) {
text = text.replace(reEmptyStringHybrid, '__t);');
}
}
if (!variable) { if (!variable) {
variable = defaults.variable; variable = defaults.variable;
text = 'with (' + variable + ' || {}) {\n' + text + '\n}\n';
// if `options.variable` is not specified or the template contains "evaluate"
// delimiters, add the data object to the top of the scope chain
if (isEvaluating || !variable) {
text = 'with (' + variable + ' || {}) {\n' + text + '\n}\n';
}
// else insert data object references to avoid using a with-statement
else {
if (variable != lastVariable) {
lastVariable = variable;
reDoubleVariable = RegExp('([(\\s])(' + variable + '\\.' + variable + ')\\b', 'g');
}
text = text
.replace(reInsertVariable, '$&' + variable + '.')
.replace(reDoubleVariable, '$1($2 || ' + variable + ')');
}
} }
text = 'function(' + variable + ') {\n' + text = 'function(' + variable + ') {\n' +
@@ -3237,6 +3279,10 @@
? ', __t' ? ', __t'
: '' : ''
) + ) +
(isEscaping
? ', __e = _.escape'
: ''
) +
(isEvaluating (isEvaluating
? ', __j = Array.prototype.join;\n' + ? ', __j = Array.prototype.join;\n' +
'function print() { __p += __j.call(arguments, \'\') }\n' 'function print() { __p += __j.call(arguments, \'\') }\n'