I have some JavaScript code that works as it should. However, I'm finding it a bit difficult to explain why it actually works. I hope, someone can make it clear to me.
I have an object that must respond to certain events, e.g. click events. Part of the object looks like this:
Maps.Marker = function (id, data, clickEvent) {
this.id = id;
this.data = data;
this.clicked = clickEvent;
};
The object is rendered in a Google map, so when the object is clicked in the map, I want to bubble the event to the clickEvent. Part of that code looks like this:
if (marker.clicked) { // click handler defined
google.maps.event.addListener(m, "click", function () {
marker.clicked();
});
}
Please note I've left out a lot of code here for brevity and know that it looks wrong as pasted here. The important thing is that the marker.clicked() function is invoked inside the Google Map event listener.
So, when my marker object is instantiated, it looks something like this:
var objClicked = function () {
if (this.data != null) {...}
...
}
var obj = new Maps.Marker("1", { "some object data" }, objClicked);
What I do not understand totally is how the this.data actually works in the objClicked function (I can access "some object data".
Can someone please explain it to me?
The reason lies in the way of the this keyword in javascript. When you assign a function to a property inside an object and you later call this function, marker.clicked(), the this inside the function is set to whatever is on the left side of the dot, which in this case is marker.
UPDATE
Here is a more thorough explanation: http://www.impressivewebs.com/javascript-this-different-contexts/
You invoke the function like this:
marker.clicked();
Because the reference to the function comes from that "clicked" property of the object that "marker" refers to, that object is used for the this value in the function. That's just how JavaScript works.
Note that if you did something weird like this:
var wrong = {};
wrong.clicked = marker.clicked;
wrong.clicked();
then your code would not work, because this would refer to that "wrong" object.
So, in general: if an object has a property, and the property value is a reference to a function, and you invoke that function via the reference, then this in the function will refer to that object. That binding happens on each individual function call; there's no permanent relationship between a function and an object. (You can get the effect of a permanent relationship with something like .bind().)
Related
Can I, in JavaScript, add a function to an already existing function or object that a function within that object then "suddenly" can "see" and call itself? Here is an example to demonstrate:
function CreateThing() {
function callAddedFunction() {
theFunction(); // this does not exist yet!
}
}
So theFunction() obviously does not exist in createThing(). Is there any way to add that outside so that when I then invoke callAddedFunction() it is able to resolve that? Something like:
let f = new CreateThing();
addFunctionAtRuntime(f, "theFunction", function() {
console.log("YAY!");
};
f.callAddedFunction();
I have tried to experiment with prototype, but I have been unable to do this. Note that the main reason for me wanting to do this is "fake" object inheritance without resorting to classes and inheritance as that requires the this keyword in front of every function call. I also want to avoid having to pass an object in that function as a parameter that can be called through in order to reach those other functions. I know that I can achieve this by having all those extra functions in global scope, but I have hoped to avoid that if possible.
EDIT: I have modified my example with the magic function I was looking for called addFunctionAtRuntime which from what I have understood is not possible. Some suggest I use eval and just make those functions available in the eval script, but so far I have been able to do this by creating a script tag dynamically and add my code as content including those functions I wanted callAddedFunction() in my example above to be able to see (without having to call through some object context).
I'm not sure this is exactly what you want but you can also use a generic higher-order function that returns the implementation you are looking for.
const supplimentor = (src, extraFunc) => ({
src: new src(),
extraFunc
})
//OR
function supplimentor1(src, extraFunc) {
this.extraFunc = extraFunc;
new src();
}
function CreateThing() {console.log('SOURCE')}
const extraFunc = () => console.log('EXTRA');
const newFunc = supplimentor(CreateThing, extraFunc)
newFunc.extraFunc()
const newFunc1 = new supplimentor1(CreateThing, extraFunc)
newFunc1.extraFunc()
Just in case the OP ...
... is not in need of something as complex as method modification as described / demonstrated at e.g.
"Can I extend default javascript function prototype to let some code been executed on every function call?"
"Intercepting function calls in javascript" ...
... why doesn't the OP just provide the very function object as parameter to the Thing constructor at the thing object's instantiation time?
After all it comes closest to (or is exactly) what the OP describes with ...
Can I, in JavaScript, add a function to an already existing function or object that a function within that object then "suddenly" can "see" and call itself?
function Thing(fct) {
this.callAddedFunction = () => fct();
}
const thing = new Thing(() => console.log("YAY!"));
thing.callAddedFunction();
I have an object function like this:
var batman = function () {
this.constructor.prototype.go = function(params){
......
}
}
When calling batman.go() I'm passing an object in with a few keys such as:
{
a:1,
b:2,
action:function(){..code to scan and inject into...}
}
My question is, how do I in batman.go() function, scan through the input param function code of 'action' and if a match is found, inject code into a certain place.
The code I am looking for is:
history.pushState({name:'homepage'},null,uri);
I want to inject so it looks like this:
history.pushState({id:an_id_variable,name:'homepage'},null,uri);
What is being inserted is:
id:an_id_variable
Use function.toString() to get the source of params.action, String.replace() to find and replace occurences of the snippet in question, and then the Function() constructor to dynamically create a new function with the amended source code:
var batman = function () {
this.constructor.prototype.go = function(params){
...
let newAction = new Function(params.action.toString().replace(
/history\.pushState\({name:'homepage'},null,uri\);/g,
`history.pushState({id:${an_id_variable},name:'homepage'},null,uri);`
));
//use newAction() however you like
}
}
It should be noted that if any end user has any amount of control over the content that can go in params.action, this would allow for completely arbitrary code injection by that user - but as pointed out in comments, arbitrary code can already be run on browsers via developer console. Just be aware of the security implications of a solution like this.
Also note that using the Function constructor binds the function to the global scope and it will lose any this context. You can bind it to an appropriate this context with function.bind() like this:
newAction = newAction.bind(params.bindTarget);
Then, when newAction executes, whatever params.bindTarget references will be this.
I'm no doubt doing something dumb here, but the following code results in an
error "this.getString is not a function."
This occurs because when unrelatedInstance calls stringGetter, "this" in showCombinedStrings() has the value of Unrelated....which actually seems fair enough, but how could this be set up so that it would work?
function BaseStringGetter() {
this.getString = function () {
return 'this is from BaseStringGetter';
}
}
function DerivedStringGetter() {
this.showCombinedStrings = function () {
console.log( 'this is from DerivedStringGetter and... ' + this.getString() );
}
}
DerivedStringGetter.prototype = new BaseStringGetter();
var stringGetterInstance = new DerivedStringGetter();
function Unrelated() {};
var unrelatedInstance = new Unrelated();
unrelatedInstance.stringGetter = stringGetterInstance.showCombinedStrings;
unrelatedInstance.stringGetter();
One option is this:
unrelatedInstance.stringGetter =
stringGetterInstance.showCombinedStrings.bind(stringGetterInstance);
unrelatedInstance.stringGetter();
Here, you're using Function.prototype.bind() to make this inside of unrelatedInstance.stringGetter() always refer back to stringGetterInstance.
The problem is how you are calling unrelatedInstance.stringGetter();
Even though stringGetter refers to the showCombinedStrings function, this inside showCombinedStrings now refers to the unrelatedInstance instance which does not have the toString() property that is why the error.
Demo: Fiddle
Here the value of this is printed as Unrelated {stringGetter: function} which is not an DerivedStringGetter instance
One easy solution is to use .bind() to give a custom execution context to unrelatedInstance.stringGetter like
unrelatedInstance.stringGetter = stringGetterInstance.showCombinedStrings.bind(stringGetterInstance);
Demo: Fiddle
Now even if you call unrelatedInstance.stringGetter(), this inside showCombinedStrings will refer to the stringGetterInstance instance.
When you call a function on an object, this will refer to the object the function is invoked on even if the function was originally defined elsewhere.
When you call unrelatedInstance.stringGetter();, this inside the function will now refer to unrelatedInstance which doesn't have getString(). The MDN this reference page has more info.
You could do something like this to preserve the original context:
unrelatedInstance.stringGetter = function() {
return stringGetterInstance.showCombinedStrings();
}
Edit: I left out bind that the other answers have now mentioned since it doesn't exist in IE8 but you'd be fine using that if you have a shim or don't care about old browsers.
I've looked around on here for an answer to this but I can't find anything that works.
Basically I'm making a tower defence game. Each tower is dynamically created and is onClick enabled. Inside the onClick listener I am trying to call a method within the class.
e.g a player clicks the tower and can choose upgrades
However the method within the listener is outputing undefined function. I know this is clearly something to do with my scope. But I can't figure out what I'm missing?
Surely it should be something like:
someListener: function(){
this.game.doSomeOtherFunction();
}
I've tried a console.log and someListener is definitely being called, but the method inside is undefined.
Thanks,
Its not working because this changes context accordingly within a callback. You can do something like this:
var self = this;
...
someListener: function(){
self.game.doSomeOtherFunction();
}
...
Or simply you could also do this:
someListener: (function () {
var callback = function(){
this.game.doSomeOtherFunction();
}
return callback.bind(this);
}())
I hope it helps.
I want to pass the name of a function "testMath" as a string into a wrapper function called "runTest" as a parameter. Then inside 'runTest' I would call the function that was passed. The reason I'm doing this is because we have a set of generic data that will populate into variables regardless of the test, then a specific test can be called, based on whatever the user wants to test. I am trying to do this using javascript/jquery. In reality the function is much more complex including some ajax calls, but this scenario highlights the basic challenge.
//This is the wrapper that will trigger all the tests to be ran
function performMytests(){
runTest("testMath"); //This is the area that I'm not sure is possible
runTest("someOtherTestFunction");
runTest("someOtherTestFunctionA");
runTest("someOtherTestFunctionB");
}
//This is the reusable function that will load generic data and call the function
function runTest(myFunction){
var testQuery = "ABC";
var testResult = "EFG";
myFunction(testQuery, testResult); //This is the area that I'm not sure is possible
}
//each project will have unique tests that they can configure using the standardized data
function testMath(strTestA, strTestB){
//perform some test
}
Do you need the function names as string? If not, you can just pass the function like this:
runTheTest(yourFunction);
function runTheTest(f)
{
f();
}
Otherwise, you can call
window[f]();
This works, because everything in the 'global' scope is actually part of the window object.
Inside runTests, use something like this:
window[functionName]();
Make sure testMath in the global scope, though.
I preffer to use apply/call approach when passing params:
...
myFunction.call(this, testQuery, testResult);
...
More info here.