Why is * this * not * this *?

I just wrote this piece of code to introduce this error that is killing me (Grrr!)

I wonder why when I get the error: method undefined I checked in Safari, and this variable inside the parserDidStart () method is not of type EpisodeController, it is type EpisodeFeedParser, why is this?

<html> <head> <script type="text/javascript"> var EpisodeFeedParser = function(url){ this.url = url; this.didStartCallback = null; }; EpisodeFeedParser.prototype.parse = function(doc){ this.didStartCallback(this); }; var EpisodeController = function(){ this.episodes = new Array(); this.parser = null; //lazy }; EpisodeController.prototype.parserDidStart = function(parser){ console.log("here *this* is not of type EpisodeController but it is EpisodeFeedParser Why?"); this.testEpi(); //**********ERROR HERE!*********** }; EpisodeController.prototype.fetchEpisodes = function(urlString){ if(urlString !== undefined){ if(parser === undefined){ var parser = new EpisodeFeedParser(urlString); parser.didStartCallback = this.parserDidStart; this.parser = parser; } this.parser.parse(); } }; EpisodeController.prototype.testEpi = function(){ console.log("it worked!"); }; function testEpisode(){ var controller = new EpisodeController(); controller.fetchEpisodes("myurl"); } </script> </head> <body> <button type="button" onclick="testEpisode()">press me</button> </body> </html> 
+6
source share
5 answers

This is often a misunderstood aspect of Javascript. (and "this", I mean this )

You can think of this as another parameter that is passed invisibly to your functions. Therefore, when you write such a function,

 function add (a,b) { return a+b; } 

you really write

 function add(this, a, b) { return a+b; } 

Most likely, it is obvious that what is not obvious is exactly what is being transmitted, and is called as "this". The rules for this are as follows. There are four ways to call a function, and each of them binds a different thing to this .

classic function call

 add(a,b); 

in a classic function call, this bound to a global object. This rule is now universally regarded as a bug and is likely to be set to null in future versions.

constructor call

 new add(a,b); 

in the constructor call, this sets up a new new object whose internal (and inaccessible) prototype pointer is set to add.prototype

method call

 someobject.add(a,b); 

in a method call, this set to some object. it doesn’t matter where you originally defined add, be it inside the constructor, part of a specific prototype of an object, or anything else. If you call the function this way, this set to any object that you called it on. This is the rule that you control.

call / call application

  add.call(someobject,a,b); 

in a call / apply call, this set to everything you pass, to the now visible first parameter of the call method.

what happens in your code:

  this.parser.didStartCallback = this.parserDidStart; 

while you wrote parserDidStart with the expectation that its this will be an EpisodeController when you call it ... what actually happens, you are now changing your this from EpisodeController to this.parser. This does not happen on this particular line of code. The switch does not physically occur until:

 this.didStartCallback(this); 

where this in this case is EpisodeParser, and by the time you run this code, you have assigned parserDidStart for the name didStartCallback. When you call didStartCallback here, with this code, you basically say ...

didStartCallback.call (this, this);

by saying this.didStartCallback (), you set it this to .. well .. this when you call it.

You should be aware of a function called bind, which is explained here: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind

Bind creates a new function from an existing function whose this fixed (bound) to any object that you explicitly pass.

+9
source

this passed to didStartCallback to

 EpisodeFeedParser.prototype.parse = function(doc){ this.didStartCallback(this); 

has type EpisodeFeedParser

and in EpisodeController.prototype.fetchEpisodes you work on EpisodeController.parserDidStart on parser.didStartCallback :

 parser.didStartCallback = this.parserDidStart; 

therefore this.didStartCallback(this); actually EpisodeController.parserDidStart(this)

and we first saw that this last this is of type EpisodeFeedParser .

QED

+2
source

Try:

 var that = this; parser.didStartCallback = function(parser) { that.parserDidStart(parser); }; 

This creates a closure that runs in the correct area before parserDidStart . Currently, when you call this.parser.parse() , it passes EpisodeFeedParser as the context, like the one from which it called. This is one of the quirks in the JavaScript field, and can be quite annoying.

+2
source

The problem is that “didStartCallBack” is called (and in context) “this”, at run time, when “this” refers to EpisodeFeedParser. I fixed it with .call (), although I'm not sure why you need to write the code for this roundabout, I am sure there must be a reason.

Important change:

 parse: function(episodeController){ this.didStartCallback.call(episodeController, this); }//parse 

Full code:

 <html> <head> <script type="text/javascript"> //An interesting context problem... //Why is it of type EpisodeFeedParser? // ---- EpisodeFeedParser var EpisodeFeedParser = function(url){ this.url = url; }; EpisodeFeedParser.prototype = { url:null, didStartCallback:null, parse: function(episodeController){ this.didStartCallback.call(episodeController, this); }//parse }//prototype // ---- EpisodeController var EpisodeController = function(){ this.episodes = new Array(); this.parser = null; //lazy }; EpisodeController.prototype = { parserDidStart: function(parser){ console.log("here *this* is not of type EpisodeController but it is EpisodeFeedParser Why?"); debugger; this.testEpi(); //**********ERROR HERE!*********** }, fetchEpisodes: function(urlString){ if(urlString !== undefined){ if(this.parser === null){ this.parser = new EpisodeFeedParser(urlString); this.parser.didStartCallback = this.parserDidStart; }//if this.parser.parse(this); }//if },//fetchEpisodes testEpi: function(){ console.log("it worked!"); } }//EpisodeController.prototype // ---- Global Stuff function testEpisode(){ var controller = new EpisodeController(); controller.fetchEpisodes("myurl"); } </script> </head> <body> <button type="button" onclick="testEpisode()">press me</button> </body> </html> 
+1
source
 EpisodeFeedParser.prototype.parse = function(doc){ this.didStartCallback(this); }; 

I don't understand why you expect this be anything other than EpisodeFeedParser .

0
source

Source: https://habr.com/ru/post/885747/


All Articles