diff --git a/CHANGELOG b/CHANGELOG index 466bb4a..a16d7a7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Performance improvements in Function methods (Samuel Lebeau, kangax, jddalton, Tobie Langel). + *1.6.0.3* (September 29, 2008) * Add support for the Chrome browser in jstest.rb. (Andrew Dupont) diff --git a/src/lang/function.js b/src/lang/function.js index 9237fb1..df2f737 100644 --- a/src/lang/function.js +++ b/src/lang/function.js @@ -1,65 +1,67 @@ Object.extend(Function.prototype, (function() { var slice = Array.prototype.slice; - function toArray(args) { - return slice.call(args, 0); - } - - function combine(array, args) { + function update(array, args) { var arrayLength = array.length, length = args.length; while (length--) array[arrayLength + length] = args[length]; return array; } + function merge(array, args) { + array = slice.call(array, 0); + return update(array, args); + } + function argumentNames() { var names = this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1] .replace(/\s+/g, '').split(','); return names.length == 1 && !names[0] ? [] : names; } - function bind() { + function bind(context) { if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; - var __method = this, args = toArray(arguments), object = args.shift(); + var __method = this, args = slice.call(arguments, 1); return function() { - var combinedArgs = combine(args.clone(), arguments); - return __method.apply(object, combinedArgs); + var a = merge(args, arguments); + return __method.apply(context, a); } } - function bindAsEventListener() { - var __method = this, args = toArray(arguments), object = args.shift(); + function bindAsEventListener(context) { + var __method = this, args = slice.call(arguments, 1); return function(event) { - var combinedArgs = combine([event || window.event], args); - return __method.apply(object, combinedArgs); + var a = update([event || window.event], args); + return __method.apply(context, a); } } function curry() { if (!arguments.length) return this; - var __method = this, args = toArray(arguments); + var __method = this, args = slice.call(arguments, 0); return function() { - var combinedArgs = combine(args.clone(), arguments); - return __method.apply(this, combinedArgs); + var a = merge(args, arguments); + return __method.apply(this, a); } } - function delay() { - var __method = this, args = toArray(arguments), timeout = args.shift() * 1000; + function delay(timeout) { + var __method = this, args = slice.call(arguments, 1); + timeout = timeout * 1000 return window.setTimeout(function() { return __method.apply(__method, args); }, timeout); } function defer() { - var args = combine([0.01], arguments); + var args = update([0.01], arguments); return this.delay.apply(this, args); } function wrap(wrapper) { var __method = this; return function() { - var combinedArgs = combine([__method.bind(this)], arguments); - return wrapper.apply(this, combinedArgs); + var a = update([__method.bind(this)], arguments); + return wrapper.apply(this, a); } } @@ -67,8 +69,8 @@ Object.extend(Function.prototype, (function() { if (this._methodized) return this._methodized; var __method = this; return this._methodized = function() { - var combinedArgs = combine([this], arguments); - return __method.apply(null, combinedArgs); + var a = update([this], arguments); + return __method.apply(null, a); }; } diff --git a/src/lang/string.js b/src/lang/string.js index f4d189c..f50e570 100644 --- a/src/lang/string.js +++ b/src/lang/string.js @@ -12,11 +12,18 @@ Object.extend(String, { } }); -Object.extend(String.prototype, { - gsub: function(pattern, replacement) { +Object.extend(String.prototype, (function() { + + function prepareReplacement(replacement) { + if (Object.isFunction(replacement)) return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; + } + + function gsub(pattern, replacement) { var result = '', source = this, match; - replacement = arguments.callee.prepareReplacement(replacement); - + replacement = prepareReplacement(replacement); + while (source.length > 0) { if (match = source.match(pattern)) { result += source.slice(0, match.index); @@ -27,78 +34,78 @@ Object.extend(String.prototype, { } } return result; - }, - - sub: function(pattern, replacement, count) { - replacement = this.gsub.prepareReplacement(replacement); + } + + function sub(pattern, replacement, count) { + replacement = prepareReplacement(replacement); count = Object.isUndefined(count) ? 1 : count; - + return this.gsub(pattern, function(match) { if (--count < 0) return match[0]; return replacement(match); }); - }, - - scan: function(pattern, iterator) { + } + + function scan(pattern, iterator) { this.gsub(pattern, iterator); return String(this); - }, - - truncate: function(length, truncation) { + } + + function truncate(length, truncation) { length = length || 30; truncation = Object.isUndefined(truncation) ? '...' : truncation; return this.length > length ? this.slice(0, length - truncation.length) + truncation : String(this); - }, + } - strip: function() { + function strip() { return this.replace(/^\s+/, '').replace(/\s+$/, ''); - }, - - stripTags: function() { - return this.replace(/<\/?[^>]+>/gi, ''); - }, + } - stripScripts: function() { + function stripTags() { + return this.replace(/<\/?[^>]+>/gi, ''); + } + + function stripScripts() { return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); - }, - - extractScripts: function() { + } + + function extractScripts() { var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); return (this.match(matchAll) || []).map(function(scriptTag) { return (scriptTag.match(matchOne) || ['', ''])[1]; }); - }, - - evalScripts: function() { - return this.extractScripts().map(function(script) { return eval(script) }); - }, + } - escapeHTML: function() { + function evalScripts() { + return this.extractScripts().map(function(script) { return eval(script) }); + } + + function escapeHTML() { var self = arguments.callee; self.text.data = this; return self.div.innerHTML; - }, + } - unescapeHTML: function() { + function unescapeHTML() { var div = new Element('div'); div.innerHTML = this.stripTags(); return div.childNodes[0] ? (div.childNodes.length > 1 ? $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) : div.childNodes[0].nodeValue) : ''; - }, - - toQueryParams: function(separator) { + } + + function toQueryParams(separator) { var match = this.strip().match(/([^?#]*)(#.*)?$/); if (!match) return { }; - + return match[1].split(separator || '&').inject({ }, function(hash, pair) { if ((pair = pair.split('='))[0]) { var key = decodeURIComponent(pair.shift()); var value = pair.length > 1 ? pair.join('=') : pair[0]; if (value != undefined) value = decodeURIComponent(value); - + if (key in hash) { if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; hash[key].push(value); @@ -107,104 +114,138 @@ Object.extend(String.prototype, { } return hash; }); - }, - - toArray: function() { - return this.split(''); - }, + } - succ: function() { + function toArray() { + return this.split(''); + } + + function succ() { return this.slice(0, this.length - 1) + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); - }, - - times: function(count) { + } + + function times(count) { return count < 1 ? '' : new Array(count + 1).join(this); - }, - - camelize: function() { + } + + function camelize() { var parts = this.split('-'), len = parts.length; if (len == 1) return parts[0]; - + var camelized = this.charAt(0) == '-' ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) : parts[0]; - + for (var i = 1; i < len; i++) camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); - + return camelized; - }, - - capitalize: function() { + } + + function capitalize() { return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); - }, - - underscore: function() { + } + + function underscore() { return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase(); - }, + } - dasherize: function() { + function dasherize() { return this.gsub(/_/,'-'); - }, + } - inspect: function(useDoubleQuotes) { + function inspect(useDoubleQuotes) { var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) { var character = String.specialChar[match[0]]; return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16); }); if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; return "'" + escapedString.replace(/'/g, '\\\'') + "'"; - }, - - toJSON: function() { + } + + function toJSON() { return this.inspect(true); - }, + } - unfilterJSON: function(filter) { + function unfilterJSON(filter) { return this.sub(filter || Prototype.JSONFilter, '#{1}'); - }, + } - isJSON: function() { + function isJSON() { var str = this; if (str.blank()) return false; str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); - }, - - evalJSON: function(sanitize) { + } + + function evalJSON(sanitize) { var json = this.unfilterJSON(); try { if (!sanitize || json.isJSON()) return eval('(' + json + ')'); } catch (e) { } throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); - }, - - include: function(pattern) { + } + + function include(pattern) { return this.indexOf(pattern) > -1; - }, + } - startsWith: function(pattern) { + function startsWith(pattern) { return this.indexOf(pattern) === 0; - }, + } - endsWith: function(pattern) { + function endsWith(pattern) { var d = this.length - pattern.length; return d >= 0 && this.lastIndexOf(pattern) === d; - }, - - empty: function() { - return this == ''; - }, - - blank: function() { - return /^\s*$/.test(this); - }, + } - interpolate: function(object, pattern) { + function empty() { + return this == ''; + } + + function blank() { + return /^\s*$/.test(this); + } + + function interpolate(object, pattern) { return new Template(this, pattern).evaluate(object); } -}); + + return { + gsub: gsub, + sub: sub, + scan: scan, + truncate: truncate, + strip: strip, + stripTags: stripTags, + stripScripts: stripScripts, + extractScripts: extractScripts, + evalScripts: evalScripts, + escapeHTML: escapeHTML, + unescapeHTML: unescapeHTML, + toQueryParams: toQueryParams, + parseQuery: toQueryParams, + toArray: toArray, + succ: succ, + times: times, + camelize: camelize, + capitalize: capitalize, + underscore: underscore, + dasherize: dasherize, + inspect: inspect, + toJSON: toJSON, + unfilterJSON: unfilterJSON, + isJSON: isJSON, + evalJSON: evalJSON, + include: include, + startsWith: startsWith, + endsWith: endsWith, + empty: empty, + blank: blank, + interpolate: interpolate + }; +})()); if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, { escapeHTML: function() { @@ -215,14 +256,6 @@ if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.proto } }); -String.prototype.gsub.prepareReplacement = function(replacement) { - if (Object.isFunction(replacement)) return replacement; - var template = new Template(replacement); - return function(match) { return template.evaluate(match) }; -}; - -String.prototype.parseQuery = String.prototype.toQueryParams; - Object.extend(String.prototype.escapeHTML, { div: document.createElement('div'), text: document.createTextNode('')