Skip to content

Commit 844d04c

Browse files
committed
perf($parse): watch variables/inputs to $parse()ed expressions and only evaluate the expression when inputs change
1 parent 19a82b3 commit 844d04c

File tree

1 file changed

+91
-23
lines changed

1 file changed

+91
-23
lines changed

src/ng/parse.js

+91-23
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ var OPERATORS = extend(createMap(), {
112112
'/':function(self, locals, a,b){return a(self, locals)/b(self, locals);},
113113
'%':function(self, locals, a,b){return a(self, locals)%b(self, locals);},
114114
'^':function(self, locals, a,b){return a(self, locals)^b(self, locals);},
115-
'=':noop,
115+
'=':true,
116116
'===':function(self, locals, a, b){return a(self, locals)===b(self, locals);},
117117
'!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);},
118118
'==':function(self, locals, a,b){return a(self, locals)==b(self, locals);},
@@ -125,7 +125,7 @@ var OPERATORS = extend(createMap(), {
125125
'||':function(self, locals, a,b){return a(self, locals)||b(self, locals);},
126126
'&':function(self, locals, a,b){return a(self, locals)&b(self, locals);},
127127
// '|':function(self, locals, a,b){return a|b;},
128-
'|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));},
128+
'|':true,
129129
'!':function(self, locals, a){return !a(self, locals);}
130130
});
131131
/* jshint bitwise: true */
@@ -492,23 +492,26 @@ Parser.prototype = {
492492
return extend(function(self, locals) {
493493
return fn(self, locals, right);
494494
}, {
495-
constant:right.constant
495+
constant:right.constant,
496+
children: [right]
496497
});
497498
},
498499

499500
ternaryFn: function(left, middle, right){
500501
return extend(function(self, locals){
501502
return left(self, locals) ? middle(self, locals) : right(self, locals);
502503
}, {
503-
constant: left.constant && middle.constant && right.constant
504+
constant: left.constant && middle.constant && right.constant,
505+
children: [left, middle, right]
504506
});
505507
},
506508

507509
binaryFn: function(left, fn, right) {
508510
return extend(function(self, locals) {
509511
return fn(self, locals, left, right);
510512
}, {
511-
constant:left.constant && right.constant
513+
constant:left.constant && right.constant,
514+
children: [left, right]
512515
});
513516
},
514517

@@ -537,12 +540,12 @@ Parser.prototype = {
537540
var left = this.expression();
538541
var token;
539542
while ((token = this.expect('|'))) {
540-
left = this.binaryFn(left, token.fn, this.filter());
543+
left = this.filter(left);
541544
}
542545
return left;
543546
},
544547

545-
filter: function() {
548+
filter: function(input) {
546549
var token = this.expect();
547550
var fn = this.$filter(token.text);
548551
var argsFn;
@@ -556,9 +559,10 @@ Parser.prototype = {
556559
}
557560
}
558561

559-
return valueFn(function $parseFilter(self, locals, input) {
562+
return extend(function $parseFilter(self, locals) {
563+
var inputVal = input(self, locals);
560564
if (args) {
561-
args[0] = input;
565+
args[0] = inputVal;
562566

563567
var i = argsFn.length;
564568
while (i--) {
@@ -568,7 +572,9 @@ Parser.prototype = {
568572
return fn.apply(undefined, args);
569573
}
570574

571-
return fn(input);
575+
return fn(inputVal);
576+
}, {
577+
children: [input].concat(argsFn || [])
572578
});
573579
},
574580

@@ -586,9 +592,11 @@ Parser.prototype = {
586592
this.text.substring(0, token.index) + '] can not be assigned to', token);
587593
}
588594
right = this.ternary();
589-
return function $parseAssignment(scope, locals) {
595+
return extend(function $parseAssignment(scope, locals) {
590596
return left.assign(scope, right(scope, locals), locals);
591-
};
597+
}, {
598+
children: [left, right]
599+
});
592600
}
593601
return left;
594602
},
@@ -688,7 +696,8 @@ Parser.prototype = {
688696
var o = object(scope, locals);
689697
if (!o) object.assign(scope, o = {});
690698
return setter(o, field, value, parserText);
691-
}
699+
},
700+
children: [object, getter]
692701
});
693702
},
694703

@@ -714,7 +723,8 @@ Parser.prototype = {
714723
var o = ensureSafeObject(obj(self, locals), parserText);
715724
if (!o) obj.assign(self, o = {});
716725
return o[key] = value;
717-
}
726+
},
727+
children: [obj, indexFn]
718728
});
719729
},
720730

@@ -781,24 +791,25 @@ Parser.prototype = {
781791
return array;
782792
}, {
783793
literal: true,
784-
constant: allConstant
794+
constant: allConstant,
795+
children: elementFns
785796
});
786797
},
787798

788799
object: function () {
789-
var keyValues = [];
800+
var keys = [], values = [];
790801
var allConstant = true;
791802
if (this.peekToken().text !== '}') {
792803
do {
793804
if (this.peek('}')) {
794805
// Support trailing commas per ES5.1.
795806
break;
796807
}
797-
var token = this.expect(),
798-
key = token.string || token.text;
808+
var token = this.expect();
809+
keys.push(token.string || token.text);
799810
this.consume(':');
800811
var value = this.expression();
801-
keyValues.push({key: key, value: value});
812+
values.push(value);
802813
if (!value.constant) {
803814
allConstant = false;
804815
}
@@ -808,14 +819,14 @@ Parser.prototype = {
808819

809820
return extend(function $parseObjectLiteral(self, locals) {
810821
var object = {};
811-
for (var i = 0, ii = keyValues.length; i < ii; i++) {
812-
var keyValue = keyValues[i];
813-
object[keyValue.key] = keyValue.value(self, locals);
822+
for (var i = 0, ii = values.length; i < ii; i++) {
823+
object[keys[i]] = values[i](self, locals);
814824
}
815825
return object;
816826
}, {
817827
literal: true,
818-
constant: allConstant
828+
constant: allConstant,
829+
children: values
819830
});
820831
}
821832
};
@@ -1044,6 +1055,9 @@ function $ParseProvider() {
10441055
parsedExpression.$$watchDelegate = parsedExpression.literal ?
10451056
oneTimeLiteralWatchDelegate : oneTimeWatchDelegate;
10461057
}
1058+
else if (parsedExpression.children) {
1059+
parsedExpression.$$watchDelegate = childExpressionWatchDelegate;
1060+
}
10471061

10481062
cache[cacheKey] = parsedExpression;
10491063
}
@@ -1057,6 +1071,60 @@ function $ParseProvider() {
10571071
}
10581072
};
10591073

1074+
function collectChildExpressions(e, list) {
1075+
if (e.children) {
1076+
for (var i = 0, ii = e.children.length; i < ii; i++) {
1077+
var child = e.children[i];
1078+
if (!child.constant) {
1079+
collectChildExpressions(child, list);
1080+
}
1081+
}
1082+
}
1083+
else if (!e.constant && -1 === list.indexOf(e)) {
1084+
list.push(e);
1085+
}
1086+
1087+
return list;
1088+
}
1089+
function isPrimitive(o) {
1090+
var t;
1091+
return null == o || (t = typeof o) === "number" || t === "string" || t === "boolean";
1092+
}
1093+
1094+
function childExpressionWatchDelegate(scope, listener, objectEquality, parsedExpression) {
1095+
var inputExpressions = collectChildExpressions(parsedExpression, []);
1096+
1097+
var inputs = [NaN];
1098+
var lastResult;
1099+
1100+
if (1 === inputExpressions.length) {
1101+
inputs = inputs[0];
1102+
inputExpressions = inputExpressions[0];
1103+
return scope.$watch(function expressionInputWatch(scope) {
1104+
var newVal = inputExpressions(scope);
1105+
if (newVal !== inputs || !isPrimitive(newVal)) {
1106+
lastResult = parsedExpression(scope);
1107+
inputs = newVal;
1108+
}
1109+
return lastResult;
1110+
}, listener, objectEquality);
1111+
}
1112+
1113+
return scope.$watch(function expressionInputsWatch(scope) {
1114+
var changed = false;
1115+
1116+
for (var i=0, ii=inputExpressions.length; i<ii; i++) {
1117+
var valI = inputExpressions[i](scope);
1118+
changed = changed || valI !== inputs[i] || !isPrimitive(valI);
1119+
if (changed) {
1120+
inputs[i] = valI;
1121+
}
1122+
}
1123+
1124+
return changed ? (lastResult = parsedExpression(scope)) : lastResult;
1125+
}, listener, objectEquality);
1126+
}
1127+
10601128
function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression) {
10611129
var unwatch, lastValue;
10621130
return unwatch = scope.$watch(function oneTimeWatch(scope) {

0 commit comments

Comments
 (0)