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.
*/
function getFunctionSource(func) {
var source = (func + '');
var source = func.source || (func + '');
return source.replace(/\n(?:.*)/g, function(match, index) {
match = match.slice(1);
return (
@@ -465,19 +465,6 @@
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
@@ -720,8 +707,8 @@
}
else {
// inline `iteratorTemplate` template
source = source.replace(/(( +)var iteratorTemplate *= *)([\s\S]+?\n\2.+?);\n/, (function() {
var code = getFunctionSource(lodash._iteratorTemplate.source);
source = source.replace(/(( +)var iteratorTemplate *= *)[\s\S]+?\n\2.+?;\n/, (function() {
var code = getFunctionSource(lodash._iteratorTemplate);
// expand properties to avoid having to use a with-statement
iteratorOptions.forEach(function(property) {
@@ -735,7 +722,6 @@
.replace(/__p *\+= *' *';/g, '')
.replace(/(__p *\+= *)' *' *\+/g, '$1')
.replace(/\+\s*' *';/g, ';')
.replace(/';(?:\\n|\s)*} *'/g, "'}'")
.replace(/(\{) *;|; *(\})/g, '$1$2')
.replace(/\(\(__t *= *\( *([^)]+) *\)\) *== *null *\? *'' *: *__t\)/g, '$1');

View File

@@ -207,7 +207,10 @@
source = source.replace(RegExp('\\.(' + propWhitelist.join('|') + ')\\b', 'g'), "['$1']");
// 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`
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`
source = source.replace('"\';\\n"', '"\';"');

156
lodash.js
View File

@@ -8,10 +8,21 @@
;(function(window, undefined) {
'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` */
var freeExports = typeof exports == 'object' && 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:
* In IE < 9 an objects own properties, shadowing non-enumerable ones, are
@@ -25,10 +36,20 @@
/** Used to restore the original `_` reference in `noConflict` */
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 */
var reNative = RegExp('^' + ({}.valueOf + '')
.replace(/[.*+?^=!:${}()|[\]\/\\]/g, '\\$&')
.replace(/valueOf|for [^\]]+/g, '.+?') + '$');
var reNative = RegExp('^' +
(ObjectProto.valueOf + '')
.replace(/[.*+?^=!:${}()|[\]\/\\]/g, '\\$&')
.replace(/valueOf|for [^\]]+/g, '.+?') + '$'
);
/** Used to match tokens in template text */
var reToken = /__token__(\d+)/g;
@@ -54,12 +75,41 @@
/** Used to store tokenized template text snippets */
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 */
try {
// Adobe's and Narwhal's JS engines will error
var useSourceURL = (Function('//@')(), true);
} 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.
* The `>` and `/` characters don't require escaping in HTML and have no
@@ -94,39 +144,6 @@
'\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
* following template settings to use alternative delimiters.
* By default, Lo-Dash uses embedded Ruby (ERB) style template delimiters,
* change the following template settings to use alternative delimiters.
*
* @static
* @memberOf _
@@ -227,7 +244,7 @@
' <% if (objectBranch) { %>\nif (length === length >>> 0) {<% } %>\n' +
' <%= arrayBranch.beforeLoop %>;\n' +
' while (<%= arrayBranch.loopExp %>) {\n' +
' <%= arrayBranch.inLoop %>;\n' +
' <%= arrayBranch.inLoop %>\n' +
' }' +
' <% if (objectBranch) { %>\n}\n<% }' +
'}' +
@@ -254,7 +271,7 @@
// the the `prototype` property of functions regardless of its
// [[Enumerable]] value.
' if (!(skipProto && index == \'prototype\')<% if (useHas) { %> && <%= hasExp %><% } %>) {\n' +
' <%= objectBranch.inLoop %>;\n' +
' <%= objectBranch.inLoop %>\n' +
' }' +
' <% } %>\n' +
' }' +
@@ -271,7 +288,7 @@
' if (shadowed[k] == \'constructor\') {' +
' %>!(ctor && ctor.prototype === <%= iteratedObject %>) && <%' +
' } %><%= 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 = {
'init': '',
'exit': 'if (!collection) return []',
@@ -568,7 +585,7 @@
*/
function tokenizeEscape(match, value) {
var index = tokenized.length;
tokenized[index] = "'+\n_.escape(" + value + ") +\n'";
tokenized[index] = "' +\n__e(" + value + ") +\n'";
return token + index;
}
@@ -582,7 +599,7 @@
*/
function tokenizeInterpolate(match, value) {
var index = tokenized.length;
tokenized[index] = "'+\n((__t = (" + value + ")) == null ? '' : __t) +\n'";
tokenized[index] = "' +\n((__t = (" + value + ")) == null ? '' : __t) +\n'";
return token + index;
}
@@ -1859,24 +1876,24 @@
*
* // basic bind
* var func = function(greeting) {
* return greeting + ': ' + this.name;
* return greeting + ' ' + this.name;
* };
*
* func = _.bind(func, { 'name': 'moe' }, 'hi');
* func();
* // => 'hi: moe'
* // => 'hi moe'
*
* // lazy bind
* var object = {
* 'name': 'moe',
* 'greet': function(greeting) {
* return greeting + ': ' + this.name;
* return greeting + ' ' + this.name;
* }
* };
*
* var func = _.bind(object, 'greet', 'hi');
* func();
* // => 'hi: moe'
* // => 'hi moe'
*
* object.greet = function(greeting) {
* return greeting + ', ' + this.name + '!';
@@ -1894,8 +1911,8 @@
methodName = thisArg;
thisArg = func;
}
// use if `Function#bind` is faster
else if (nativeBind) {
// use if native `Function#bind` is faster
else if (useNativeBind || (nativeBind && arguments.length > 2)) {
return nativeBind.call.apply(nativeBind, arguments);
}
@@ -1929,7 +1946,6 @@
}
return func.apply(thisBinding, args);
}
return bound;
}
@@ -3185,7 +3201,8 @@
// https://github.com/olado/doT
options || (options = {});
var isEvaluating,
var isEscaping,
isEvaluating,
isInterpolating,
result,
defaults = lodash.templateSettings,
@@ -3207,7 +3224,7 @@
// tokenize delimiters to avoid escaping them
if (escapeDelimiter) {
text = text.replace(escapeDelimiter, tokenizeEscape);
isEscaping = text != (text = text.replace(escapeDelimiter, tokenizeEscape));
}
if (interpolateDelimiter) {
isInterpolating = text != (text = text.replace(interpolateDelimiter, tokenizeInterpolate));
@@ -3225,10 +3242,35 @@
// clear stored code snippets
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) {
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' +
@@ -3237,6 +3279,10 @@
? ', __t'
: ''
) +
(isEscaping
? ', __e = _.escape'
: ''
) +
(isEvaluating
? ', __j = Array.prototype.join;\n' +
'function print() { __p += __j.call(arguments, \'\') }\n'