From 2e33e89a77d115ff17f5841ec328b1c1e4228161 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Sun, 30 May 2010 19:42:21 -0700 Subject: added compiled getterFN for better performance --- scenario/perf.html | 2 +- src/Parser.js | 7 +++---- src/Scope.js | 35 +++++++++++++++++++++++++++++++++++ test/ScopeSpec.js | 21 +++++++++++++++++++++ 4 files changed, 60 insertions(+), 5 deletions(-) diff --git a/scenario/perf.html b/scenario/perf.html index 1b0e40b4..e4edc00f 100644 --- a/scenario/perf.html +++ b/scenario/perf.html @@ -25,7 +25,7 @@
diff --git a/src/Parser.js b/src/Parser.js index cfb72c72..df270792 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -151,9 +151,7 @@ Lexer.prototype = { } var fn = Lexer.OPERATORS[ident]; if (!fn) { - fn = function(self){ - return getter(self, ident); - }; + fn = getterFn(ident); fn.isAssignable = ident; } this.tokens.push({index:start, text:ident, fn:fn}); @@ -563,8 +561,9 @@ Parser.prototype = { fieldAccess: function(object) { var field = this.expect().text; + var getter = getterFn(field); var fn = function (self){ - return getter(object(self), field); + return getter(object(self)); }; fn.isAssignable = field; return fn; diff --git a/src/Scope.js b/src/Scope.js index bed0ff6d..1c223130 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -43,6 +43,41 @@ function setter(instance, path, value){ return value; } +/////////////////////////////////// + +var getterFnCache = {}; +function getterFn(path){ + var fn = getterFnCache[path]; + if (fn) return fn; + + var code = 'function (self){\n'; + code += ' var last, fn, type;\n'; + foreach(path.split('.'), function(key) { + code += ' if(!self) return self;\n'; + code += ' last = self;\n'; + code += ' self = self.' + key + ';\n'; + code += ' if(typeof self == "function") \n'; + code += ' self = function(){ return last.'+key+'.apply(last, arguments); };\n'; + if (key.charAt(0) == '$') { + // special code for super-imposed functions + var name = key.substr(1); + code += ' if(!self) {\n'; + code += ' type = angular.Global.typeOf(last);\n'; + code += ' fn = (angular[type.charAt(0).toUpperCase() + type.substring(1)]||{})["' + name + '"];\n'; + code += ' if (fn)\n'; + code += ' self = function(){ return fn.apply(last, [last].concat(slice.call(arguments, 0, arguments.length))); };\n'; + code += ' }\n'; + } + }); + code += ' return self;\n}'; + fn = eval('(' + code + ')'); + fn.toString = function(){ return code; }; + + return getterFnCache[path] = fn; +} + +/////////////////////////////////// + var compileCache = {}; function expressionCompile(exp){ if (isFunction(exp)) return exp; diff --git a/test/ScopeSpec.js b/test/ScopeSpec.js index a3b6d9ae..d93400e5 100644 --- a/test/ScopeSpec.js +++ b/test/ScopeSpec.js @@ -157,4 +157,25 @@ describe('scope/model', function(){ } }); }); + + describe('getterFn', function(){ + it('should get chain', function(){ + expect(getterFn('a.b')(undefined)).toEqual(undefined); + expect(getterFn('a.b')({})).toEqual(undefined); + expect(getterFn('a.b')({a:null})).toEqual(undefined); + expect(getterFn('a.b')({a:{}})).toEqual(undefined); + expect(getterFn('a.b')({a:{b:null}})).toEqual(null); + expect(getterFn('a.b')({a:{b:0}})).toEqual(0); + expect(getterFn('a.b')({a:{b:'abc'}})).toEqual('abc'); + }); + + it('should map type method on top of expression', function(){ + expect(getterFn('a.$filter')({a:[]})('')).toEqual([]); + }); + + it('should bind function this', function(){ + expect(getterFn('a')({a:function($){return this.b + $;}, b:1})(2)).toEqual(3); + + }); + }); }); -- cgit v1.2.3