I am trying to change myprop's value with option 3 and 4, but with no success. Why?
object1 = {myprop:"value1", ....};
button1.addEventListener('click', function(e){
object1.myMethod("value2"); // 1) working. myMethod is a method that sets myprop's value
object1.myprop="value2"; // 2) working
myFunction1(object1.myMethod); // 3) not working
myFunction2(object1.myprop); // 4) not working
});
function myFunction1(src) {
src("value2");
}
function myFunction2(src) {
src="value2";
}
You are passing the value of object1.myMethod/object.myprop which gets copied to the src variable.
You then either call the function (but in a different context so the value of this inside it is different) or overwrite the value of src while leaving the original property alone.
If you want the function to be called in the right context, you need to pass that context in with call() or apply().
If you want to overwrite the myprop property of the object stored in object1 then you have to have a reference to the value of object1 and set the property on that.
When you define your anonymous function to handle the click event (button1.addEventListener('click', function(e) {...}), the value of object1.myMethod and object1.myprop are captured in the closure.
What it means is: you are defining a function, and this function, when executed, will need some values that are in scope when it was defined, but not when it will be executed (both object1.myMethod and object1.myprop). The function is being defined right there in your code, but is being passed around as a parameter and will be called far away from there, whenever the click event happen on the button.
What happens is that that object1.myMethod and object1.myprop are evaluated to its values and captured in the function's closure, to be available later when the function will be executed (you don't see any of this happening, it just happens).
So, your approach won't work here. I see one alternative for setting the property, that is pass the object in which you want to change the value, and pass the name of the property that you want to change.
// 1 way to make the property call work
object1 = {myProp:"value1"};
button1.addEventListener('click', function(e){
myFunction2(object1, 'myProp');
});
function myFunction2(src, prop) {
src[prop]="value3";
};
For the methods parts, I can see 3 ways to make it work (actually 4, if I've used apply instead of call).
First is the same as in the case of the property: you pass the object1 and the name of the slot where is the function.
Second, you can pass the object1 and the function, and later call your function in the context of object1.
Third, you can create, from your object1.myMethod, a new function that will be bound to object1 (that is, object1 will be its context), no matter where you will call it later. That is done with bind.
Examples:
// 3 ways to make the method call work
object1 = {myMethod:function(someParam){console.log(someParam)}};
button1.addEventListener('click', function(e){
myFunction1(object1, 'myMethod');
myFunction3(object1, object1.myMethod);
myFunction4(object1.myMethod.bind(object1, 'value3'));
});
function myFunction1(src, prop) {
src[prop]("value3");
};
function myFunction3(src, method) {
method.call(src, 'value3');
};
function myFunction4(func) {
func();
};
Related
I read some other answer about this topic but I'm not sure I understand how this keyword works inside addEventListener.
const button = document.querySelector('button');
function foo() { console.log(this) }
button.addEventListener('click', foo);
foo is a regular function inside addEventListener, it's not a method on button object. When foo is called should be executed in the context of the global object, therefore this should be equal to window and not to button.
Looks like a situation similar to this example:
const obj = {
method: function (cb) {
console.log('method', this); // `this` === `obj`
return cb();
}
};
obj.method(function() {
console.log('cb', this); // `this` === `window`
});
Where obj could be considered as button, method could be addEventListener and cb the callback inside addEventListener.
I know I can use bind to change the context of this but I want to understand more in depth why it works like that.
Why this inside addEventListener callback is invoked on the context of the current element instead of the global object?
If we are using functions which have been defined using function keyword as an event handler, then that event handler function executes in the context of the element on which event was binded
button.addEventListener('click', foo);
so, in this case, this value inside foo will be button element.
If we use arrow functions instead of them then this value will be the window object
The reason is this in an arrow function has the same value as the context in which the arrow function was created
button.addEventListener('click', () => { console.log(this) // window } );
More about lexical this
What is lexical 'this'?
While we know that event listeners are executed with 'this' set to the event target, the below lines of code inside the EventTarget.prototype.dispatchEvent method in the EventTarget link that you found will answer your question as to how it is implemented.
for (var i = 0, l = stack.length; i < l; i++) {
stack[i].call(this, event);
}
The 'stack' array has the callback functions and they are invoked using .call by passing in the event target instance (this) and event as arguments.
As event handler is a type of callback, they are passed as a parameter to the function. Let's create a simple function and passed one callback as a parameter to it and see how it actually works.
function testCallBack(fn){
console.log('inside testCallBack');
fn('Hello I am a callBack')
}
testCallBack(foo);
function foo(param){
console.log(param);
}
// Outputs:
inside testCallBack
Hello I am a callBack
Every scope in JavaScript has a this object that represents the calling object for the function.
That's the reason why this inside addEventListener callback is invoked on the context of the current element instead of the global object.
Refer below code for more clear understanding:
function sayNameForAll() {
console.log(this.name);
}
var person1 = {
name: "Rajat",
sayName: sayNameForAll
};
var person2 = {
name: "pldg",
sayName: sayNameForAll
};
var name = "Sidd";
person1.sayName(); // outputs "Rajat" here calling object is person1, so this represents person 1
person2.sayName(); // outputs "pldg"
sayNameForAll(); // outputs "Sidd"
So when you call button.addEventListner('click',foo), your calling object is button.
Event listeners are executed with this set to the object that triggered the event, as one listener can listen to events of many objects.
A regular function invocation however does not set this if the invocation expression does not contain a member access via .. In those cases, without "use strict" active, this will become the global context, which is window in the browser.
If you want this for cb to be obj, you could replace cb() with cb.apply(this), which would set cb's this to that of the enclosing function.
A final warning: these this mechanics only work for functions defined with the function keyword (and similar mechanics). The this inside of an arrow function becomes locked to that of the enclosing scope at the time of definition.
Just like you can use bind or call to set this to whatever you want, the browser APIs can also call your functions with any value set to this. It’s used in a bunch of weird ways and isn’t very consistent. Outside of classes and methods, this is more like a secret extra argument to a function. In this case you could avoid needing it by getting the button element from event.target.
I have the following problem:
I'm trying to overwrite a function to apply it then with angular ($scope.$apply()), but my this-context doesn't seem to be the right one.
The original function (in another file) looks like the following:
Anno.prototype.switchTo = function(otherAnno) {
if (otherAnno != null) {
this.hideAnno();
return otherAnno.show();
} else {
console.warn("Can't switchTo a null object. Hiding instead.");
return this.hide();
}
};
And then in another file I "overwrite" it like the following:
var switchToFunction = AnnoModule.Anno.prototype.switchTo;
AnnoModule.Anno.prototype.switchTo = function(otherAnno) {
switchToFunction(otherAnno);
$scope.$apply();
};
So actually I save the original function, then redefine the original function to call the original one and then apply the scope.
Now comes the problem: As you can see, the function uses this.hideAnno() in it, but in my redefined function, the context is another one, that's why chrome is throwing an error saying "this.hideAnno() is not a function". But now I'm not sure how I can get the right context. I tried to understand this, but I find JavaScript is so confusing that I really don't get it.
Can somebody help me understand that JavaScript confusion?
When a function is called as a method in js, the this inside of it refers to the object the method belongs to.
On the other hand, when a function is called on its own, this inside of it refers to the global object or undefined in strict mode.
You are extracting (and then calling) a function defined as a method into a standalone function, that's why this doesn't do what you expect it to.
What you need in this case is to call or apply your switchToFunction, setting the value of this to what you need. In other words you set the this of the old method to be the this of the new method you created:
var switchToFunction = AnnoModule.Anno.prototype.switchTo;
AnnoModule.Anno.prototype.switchTo = function(otherAnno, that) {
switchToFunction.call(this, otherAnno); // sets `this` of the old method to be this of the new method you created
$scope.$apply();
};
To understand the problem, I think first we should understand how this keyword works and how it can be tweaked.
In JavaScript the this object inside of any function will be the object on which the method is invoked.
Consider these following example,
var obj1 = {
foo: function() {
console.log(this);
}
};
function bar() {
console.log(this);
}
Now when the methods are invoked we get output like below,
obj1.foo(); // obj1
bar(); // window
Because foo method is invoked on obj1, so this inside of foo method became obj1. Similarly this inside bar method will be window object.
Now, what if I want to invoke the bar method with obj1 as this inside the function. For this JavaScript provides call, apply and bind methods to change the this of function dynamically. Let us see how we can achieve this using call method.
bar.call(obj1); // obj1
Similarly,
obj1.foo.call(window); // window
call method takes an thisArg object as argument which will be this inside the bar function. And if bar function also expects arguments that also can be passed through call method following thisArg. See MDN for information about call.
So the solution for your problem will be,
var switchToFunction = AnnoModule.Anno.prototype.switchTo;
AnnoModule.Anno.prototype.switchTo = function(otherAnno) {
switchToFunction.call(this, otherAnno);
$scope.$apply();
};
Hope this makes it clear for you to understand the problem.
I've read everything about js closures but can't understand the following code:
function main(condition){
var a;
if (condition){
a="aaa";
}
else
{
a="bbb";
return;
}
button.addEventListener("click", function(){ alert(a);});
}
main(true);
main(false);
After that click button. The result is:"aaa". Doest this mean that for nested function doesn't keep the reference to variables but copy them? Please explain.
The first time you run main, you assign "aaa" to a (which is a local variable) and then bind an event handler which reads that variable.
The second time you run main, you assign "bbb" to a (since this is a different invocation of the function, it is a different local variable). You do not bind an event handler that reads that variable since you return first.
When the click event fires, the function it runs is in the scope of the a from the first call, not the second call, so the value is "aaa".
I would say that this is what closures are all about. Closures are "internal functions" that retain access to the values of variables that were "valid" at the moment when the internal function was created even after the "outer function" returned.
Consider the following example:
var outerFunc = function(a) {
return function (x) {
return x+a;
}
} //the outer function returns a function
Now let's invoke the outer function and store the returned value in the variable newFunc:
var newFunc = outerFunc(15); //the outer function has been invoked, newFunc is a function
newFunc(1); //let's invoke the resulting "inner function"
The result will be 16. The inner function will always remember the value 15, even if you invoke the outer function with other argument values. This is how JavaScript works.
In your example with the event listener something very similar is happening. The inner function (with the alert) is registered as a reaction to the click event, and it always 'remembers' the value of a. When you press the button, this invokes this 'inner' function.
To get the behavior you may want, try:
var main = function () {
var a;
return function() {
if (condition){
a="aaa";
}
else
{
a="bbb";
return;
}
button.addEventListener("click", function(){ alert(a);});
};
}();
main(true);
main(false);
This works because it defines a in a way which persists across invocations of the function, which as another answerer pointed out, is the problem.
that is not a closure, its just a double call to a function, when its true it adds the event listener so that it prints 'aaa' but second time it isn't added an event, because it returns before adding it.
Take this very simple framework I am experimenting with (so that I can learn JavaScript's function prototype more in depth.)
(function(){
var app = {
ui: {
app: document.querySelector('.app'),
settings: document.querySelector('.settings'),
},
actions: 'click settings openSidebar'
,
functions: {
openSidebar: function(e){
console.log(this); // <- expected value of this is 'app.ui'
}
},
run: function(){
var a1 = this.actions.split("\n");
var a2 = this.actions.split(" ");
var self = this;
this.ui[a2[1]].addEventListener(a2[0], function(e){
app.functions.openSidebar.call(self.ui,e);
});
}
};
app.run();
})();
This works great. Output from console is:
Object {ui: Object, actions: "click settings openSidebar", functions: Object, run: function}
However, when I try to do it like this:
var self = this;
this.ui[a2[1]].addEventListener(a2[0], function(e){
app.functions.openSidebar(e);
}.bind(this));
The context of this in openSidebar() is openSidebar (aka bind has no effect). Console output:
Object {openSidebar: function}
However, when I try to use the apply function, like so:
app.functions.openSidebar.apply(self.ui,e);
It works fine (this in openSidebar is app.ui) EXCEPT that the argument (e) does not get passed, so e == undefined.
Here goes:
1. Why does not bind work (at all) in the first example?
2. Why does apply work without passing arguments (the e (event))?
And for added brownie points:
3. Why does call work as expected?
Why does not bind work (at all) in the first example?
It "works", it just doesn't do what you expect.
Inside your anon function this is indeed the value set by bind. However, when you then call a function that is also a property of an object (functions.openSidebar) then for that invocation this is automatically bound to that object inside the function (i.e. this === functions). The value of this from the parent context is never "inherited" down the call chain.
Why does apply work without passing arguments (the e (event))?
Because apply tries to pull out the arguments for the call from its own second argument e by treating it as an array. This means that its length property is checked first; your event object doesn't have a length so apply gets the undefined value produced and effectively behaves as if you had passed in an empty array. See the annotated ES5 reference for the details.
Why does call work as expected?
This is a strange question. Why would it not work? You are using it exactly like it's meant to be used.
You'll need to do this:
this.ui[a2[1]].addEventListener(a2[0], app.functions.openSidebar.bind(this));
bind returns you a new function with a manually set context of whatever you pass in. Because you're not using bind on the right function, you're calling your function with app.functions to the left of the invoked function, which henceforth is known as this inside the invoked function!
Apply takes arguments as an array, not named parameters...
I can't explain the third without saying that that is how call works!
1) I have an inner function that is event driven.
2) The inner function depends on variables in the outer function.
3) The variables in the outer functions are parameters to the outer function.
4) The outer function is being runned several times.
q) What values can I excepct the parameter variables from the outer function to have when the inner function gets triggered later on in the code?
scenario 1) When triggering the inner function, the exact state of the outer function that existed when creating the inner function is used.
scenario 2) When triggering the inner function, the latest value from the outer functions is used.
example:
function outerFunction(parameter) {
var object = new Object();
object.on('click', function () {
alert(parameter);
});
return object;
}
Each time outerFunction is called, a new event listener is registered for clicks on object. So, if you call:
outerFunction('foo');
outerFunction('bar');
you should see two alerts, one for 'foo', and one for 'bar'.
In case of jQuery (and, probably, similar libraries) each time you call on(evt, handler) you bind new handler to element for that event. And jQuery stacks handlers.
So if you do:
outerFunction(1);
outerFunction(2);
jQuery will bind two handlers, and when triggering 'click' on your object you will get, in sequence:
Alert for "1".
Alert for "2".
Fiddle for that: http://jsfiddle.net/ajWvk/
If it is your implementation of some abstract event-binding, and your implementation doesn't stack handlers, you will just bind new handler on each invocation of outerFunction, erasing all previous handlers.
In this case, calling:
outerFunction(1);
outerFunction(2);
will only alert "2", when "click" is triggered on object.
It depends on what parameter is. In javascript, strings and numbers are passed by value but objects are passed by reference. So if parameter is a string then it will work like bfavaretto said. However, if parameter is an object and you alert on parameter.text, then you will be alerted with whatever the current value of parameter.text is.
Try defining these in the console:
function outerFunction(parameter) {
setTimeout(function () {
console.log(parameter);
},5000);
}
function otherOuterFunction(parameter) {
setTimeout(function () {
console.log(parameter.text);
},5000);
}
Now, when you run this line it will log "test":
parameter = "text"; outerFunction(parameter); parameter = "hi";
But this will log "hi":
obj = {text: "text"}; otherOuterFunction(obj); obj.text = "hi";