I am doing performance testing in Angular and I want to know exactly how many watches are there in my page. Turns out there is no easy way to do this. Has anyone tried it yet?
Any help will be highly appreciated!
I had the same question. I created a function that will do it:
// get the watch count
// scopeHash is an optional parameter, but if you provide one, this function will modify it for later use (possibly debugging)
function getWatchCount (scope, scopeHash) {
// default for scopeHash
if (scopeHash === undefined) {
scopeHash = {};
}
// make sure scope is defined and we haven't already processed this scope
if (!scope || scopeHash[scope.$id] !== undefined) {
return 0;
}
var watchCount = 0;
if (scope.$$watchers) {
watchCount = scope.$$watchers.length;
}
scopeHash[scope.$id] = watchCount;
// get the counts of children and sibling scopes
// we only need childHead and nextSibling (not childTail or prevSibling)
watchCount+= getWatchCount(scope.$$childHead, scopeHash);
watchCount+= getWatchCount(scope.$$nextSibling, scopeHash);
return watchCount;
}
It will calculate the number of watchers on any scope. It may be most useful to calculate on the root scope, but you can use it at any scope level (possibly to check watches on a component). Here is an example in action: http://jsfiddle.net/S7APg/
Related
Edit: the code below was made up on the spot to show how I was going about what I was doing. It definietely won't run, it is missing a lot of things.
Here is a working example in codepen: https://codepen.io/goducks/pen/XvgpYW
much shorter example: https://codepen.io/goducks/pen/ymXMyB
When creating a function that is using call or apply, the this value stays null when using getPerson. however, when I use apply or call with getPerson it returns the correct person.
Please critique, I am really starting to learn more and more. I am in the middle of a project section so it might be hard to change all the code, but my next project could implement this better.
call and apply are setting to the window and not the object.
I will provide code that is much simpler with the same concept of what I am talking about.
function createPerson(){
this.manager = null;
this.teamManager = null;
this.setTeamManager = function(val){
this.teamManager = val;
}
this.setManager = function(val){
console.log('setting manager to',val);
this.teamManager = val;
}
this.getTeamManager = function(){
console.log('setting team manager to',val);
return this.teamManager ;
}
this.getManager = function(){
return this.manager;
}
this.appendSelect = function(elem){
var that = this;
createOtherSelects(that,elem);
}
//some functions that create selects with managers etc
//now assume there are other selects that will filter down the teams,
//so we might have a function that creates on change events
function createOtherSelects(that){
//code that creates locations, depending on location chosen will
//filter the managers
$('#location').on('change',function(){
//do some stuff
//... then call create management
createManagement(that,elem);
});
}
function createManagement(that,elem){
var currentLocation = that.location; //works
var area = that.area;//works ... assume these are set above
//code that returns a filter and unique set of managers back
that.teamManager = [...new Set(
data.map(person=>{
if(person.area==area &&
person.currentLocation==currentLocation
){
return person;
}
})
)].filter(d=>{if(d){return d}});
if(elem.length>0){
var selectNames = ['selectManager','selectTeamManager'];
var fcns = [that.setManager,that.setTeamManager];
for(var i = 0; i < selectNames.length;i++){
//do stuff
if(certainCriteriaMet){
// filter items
if(filteredManager == 1){
fcns[i].call(null,currentManager);//
}
}
}
}
}
}
var xx = new createPerson()
In console I see setting manager and setting team manager to with the correct values.
however when I call xx in console, I see everything else set except for
xx.teamManager and xx.manager
instead it is applying to the window, so if I type teamManager in the console, it will return with the correct person.
If I straight up say
that.setManager('Steve')
or even it works just fine.
xx.setManager('steve')
the this value in setManager is somehow changing from the current instance of the object to this window. I don't know why, and I would like to learn how to use apply and call using that for future reference.
I think the issue is with your following code
fcns[i].call(null,currentManager)
If you are not supplying "this" to call, it will be replaced with global object in non-strict mode.
fcns[i].call(that,currentManager)
See mdn article here
From your codepen example, you need to change that line
fcnset[0].apply(that,[randomName]);
The first argument of the apply method is the context, if you are not giving it the context of your method it's using the global context be default. That's why you end up mutating the window object, and not the one you want !
I'm fairly new to angular and directives but while I was making a custom directive for my app I realized I was using variables and scope interchangeably with no issue.
For example I have scope.onBreak = false and var completedSessions = 0
My question is, when should I use scope and when should I use variables inside a directives and will this end up causing issues in the future if I don't use them accordingly .
scope.onBreak = false;
scope.onLongBreak = false;
// starts countdown from current work/break time
scope.timerText = "Work Timer";
var completedSessions = 0;
var timeSet;
var setBreak = function() {
$interval.cancel(timeSet);
scope.workTime = MY_TIMES.break;
scope.buttonText = "START";
scope.onBreak = true;
scope.timerText = "Break Timer";
};
scope.countdown = function() {
if (scope.workTime <= 0) {
//if countdown reaches 0 and is on break , set time to 25m (work)
if (scope.onBreak) {
console.log("currently working");
setWork();
} else {
setBreak();
}
}
} else {
//countdown
scope.workTime--;
}
};
I think a good way of looking at it is to say that 'scope' is the malleable (and manipulatable) link between controller and view (template). Variables are internal to the class you're working on be it controller, directive, service etc., and ONLY seen by those classes (a template file cannot access a declared variable called 'foo' on the controller). There are probably more eloquent explanations, but since no-one had posted anything, thought I would.
You should use variables whenever you don't need to pass the data to the view, as it's faster.
If you need to use that data in a view, then use scope.
scope variables bind to the DOM view. But var can't bind, And functionality wise only we can use within JS.
I've been messing with the Batarang plugin recently to analyze some performance. I notice that at the top of every log there is a section dedicated to something called regularInterceptedExpression. Can anybody explain what this means and what are some ways to improve the performance. I read somewhere that is could be from using the '=' property in directives. If anyone else has seen this, is there a solution?
If you dig into AngularJS code, you can see function regularInterceptedExpression(scope, locals, assign, inputs) defined inside functionaddInterceptor(parsedExpression, interceptorFn). The only place where function addInterceptor(parsedExpression, interceptorFn) is used is function $parse(exp, interceptorFn, expensiveChecks). This is where the String and other watches are converted to functions. You need to update the angular.js file to
1) enhance the $parse(exp, interceptorFn, expensiveChecks) function to keep the source of the parsing:
Find the end of the method and each switch case end update with setting the $$source to the first argument of addInterceptor function.
parsedExpression.$$source = exp; // keep the source expression handy
return addInterceptor(parsedExpression, interceptorFn);
case 'function':
exp.$$source = exp; // keep the source expression handy
return addInterceptor(exp, interceptorFn);
default:
noop.$$source = exp; // keep the source expression handy
return addInterceptor(noop, interceptorFn);
2) inside the regularInterceptedExpression function collect the statistics of
calls to that function:
var fn = regularWatch ? function regularInterceptedExpression(scope, locals, assign, inputs) {
var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs);
window.$$rieStats = window.$$rieStats || {};
window.$$rieStats[parsedExpression.$$source] = (window.$$rieStats[parsedExpression.$$source] ? window.$$rieStats[parsedExpression.$$source] : 0) + 1;
return interceptorFn(value, scope, locals);
3) run you application and inspect the statistics i.e. open the Development Tools and write $$rieStats into the JavaScript console. You should see the numbers of watchers being called by the regularInterceptedExpression function.
Object.keys($$rieStats).sort(function(a,b){return $$rieStats[a]-$$rieStats[b]}).reverse().forEach(function(item){ console.log(item, $$rieStats[item])})
HINT: you can also add the $$rieStats counting to the other branch function oneTimeInterceptedExpression to track to one time binding as well.
I have two functions, one working, the other not.
They are equal, except that the one is looping through a variable, in which the global object of the scope is saved (hope this makes sense), and the other tries to loop through the text directly, but fails, because it throws the error:
Uncaught TypeError: Cannot read property '0' of undefined
Here is the fiddle:
http://jsfiddle.net/4p1p4wjy/2/
In my understanding, the 2nd version of the function is not working, because it somehow can't access the this.splittedText, from within the callback of the function.
First Working Function:
loopThroughSplittedText: function() {
// delete this
var locationInString = 0;
var splittedText = this.splittedText;
function delayedOutput() {
document.getElementById('output').innerHTML = splittedText[locationInString];
locationInString++;
if(locationInString < splittedText.length) {
setTimeout(delayedOutput, 200);
}
}
delayedOutput();
},
Second Not Working Function:
loopThroughSplittedTextNotWorking: function() {
// delete this
var locationInString = 0;
function delayedOutput() {
document.getElementById('output').innerHTML = this.splittedText[locationInString];
locationInString++;
if(locationInString < this.splittedText.length) {
setTimeout(delayedOutput, 200);
}
}
delayedOutput();
}
How do I make the 2nd function work, without saving the object inside a local variable first? I'd like to use the two-way databinding as best as possible.
How do I make the 2nd function work, without saving the object inside a local variable first?
You can't. this is a variable that is always local to the function it is used in, and its value depends on how the function is called. If you want to use its value in a different function, then you need to copy it into another variable.
The bind method provides a shorthand for doing that.
setTimeout(delayedOutput.bind(this), 200);
Simple answer, you don't.
Because your function is called through timeout, it's not in the same context anymore and 'this' will not refer to the same object anymore.
You can do this:
loopThroughSplittedTextNotWorking: function() {
// delete this
var locationInString = 0;
var that = this;
function delayedOutput() {
document.getElementById('output').innerHTML = that.splittedText[locationInString];
locationInString++;
if(locationInString < that.splittedText.length) {
setTimeout(delayedOutput, 200);
}
}
delayedOutput();
}
By saving the "this" variable into a local variable, you can access it in your "delayedOutput" function.
I realize it's basically just like your working example, just phrased a little different, but that's usually how I do it.
I'm doing a Linked List data structure. The prototype includes a method to pop (delete) the last item from the list which I'm attempting to do by finding the last object, and then setting it to null. It does not seem to work. What does work is setting the reference (the 'pointer') in the previous object to null. I'm still a relative JS OOP newbie, can't get my brain to understand why. The code:
function LinkedList() {
this._rootNode = null;
this._length = 0;
}
LinkedList.prototype = {
push: function(data) {
var newNode = {
data: data,
nextNode: null
};
// initialize this._rootNode or subsequent .nextNode with newNode
this._length++;
},
pop: function() {
var selectedNode, perviousNode;
if ( this._rootNode ) {
if ( this._length > 1 ) {
selectedNode = this._rootNode;
while ( selectedNode.nextNode ) {
previousNode = selectedNode; // <-- shouldn't need this?
selectedNode = selectedNode.nextNode;
}
selectedNode = null; // <-- doesn't delete it
// previousNode.nextNode = null; // <-- works (but feels unnecessary?)
} else {
this._rootNode = null;
}
this._length--;
}
},
// more methods..
};
/* --- Main Prorgam --- */
var list = new LinkedList();
list.push('AAA');
list.push('BBB');
list.pop();
console.log(list._rootNode.nextNode.data); <-- 'BBB' still there
Would appreciate some insight, and any other tips on improving the function. Thanks!
I guess you realize that your push method doesn't work, but you haven't asked about that one.
If you are doing some kind of school project that requires you to write a linked list like this, then by all means, continue. Your issue is that selectedNode is not really "the node itself", it's a reference to it, and you're just setting that reference to null while the previous item's nextNode pointer still refers to it, so you haven't actually removed it from your list. You would actually do so by un-commenting the line setting that pointer to null, which means you also have to leave in the line saving the reference to the previous node.
previousNode.nextNode = null;
You actually don't want to delete the node entirely with pop(), you want to return it. Once you remove the reference to the popped node in your calling function though, it will be the last reference and the object will be made available for garbage collection. This is (to my knowledge) how all traditional OOP languages handle linked lists at the basic level.
Which brings me to my next point, that most OOP languages you'll use these days don't actually require you to work on the basic level. Most of them have libraries that will implement linked lists for you and Javascript in particular essentially implements a linked list-style data structure in its array syntax. To the point where ([1,2,3,4]).pop() evaluates to 4 and ([1,2,3,4]).push(5) evaluates to [1,2,3,4,5]. If you actually need to USE a linked list in a real project, just don't.