javascript how to get this.variable in callback function - javascript

In the following customized class in javascript, in callback, why does this.obj have nothing but local variable obj has thing I want? Thanks.
function ClassTest(director) {
this.obj = {"test1": "test1"};
}
function test1(input, callback) {
callback("success");
}
ClassTest.prototype.test = function() {
var obj = this.obj;
test1("niuniu",function(e){
console.log(this.obj); // undefined
console.log(obj); // this one has stuff
});
}
// run
new ClassTest().test()

Because the function inside test1 is creating a new scope with different this context. Typical solutions are to bind or to cache this:
Binding:
test1("niuniu",function(e){
console.log(this.obj);
}.bind(this));
Caching:
var self = this;
test1("niuniu",function(e){
console.log(self.obj);
});

As for this line of code:
console.log(obj); // this one has stuff
The reason it works has to do with how JavaScript closure works. The code defined in your anonymous function has access to all variables in its local scope as well as variables defined in encompassing scopes and therefore obj is available. See How do JavaScript closures work? for more on closure.
The keyword this however, is a reference to the current scope. Because you are accessing this.obj from within the anonymous function, this refers to the anonymous function itself - which has no obj property defined. In the enclosing function, which is extending the ClassTest prototype, this refers to the current ClassTest object, which does have a obj property defined.

Related

Function property goes in global variables for no reason

I'm using JQuery, latest version.
$(document).ready(function(){
function ViewModel(){
this.Impossible = "impossible!";
}
let vm = ViewModel();
});
$(window).on("beforeunload", function (e) {
console.log("this: " + this.Impossible);
console.log("window: " + window.Impossible);
// return "Test";
});
https://jsfiddle.net/5wkdxvhq/1/
How is it possible that "impossible!" gets logged? Impossible is wrapped by at least 2 functions, it definitely shouldn't be a global variable, yet there it is.
Is this a behavior of JQuery or JavaScript?
Is this standard behavior that I can rely on? Or is it some quirk that won't work in other browsers/future Knockout updates or a bad design practice by me?
Impossible isn't wrapped up by your functions declarative/lexical environments. It isn't local to your innermost function. If it was declared like this:
let Impossible = "impossible!"
//or
//var Impossible = "impossible!"
Then Impossible would have been local to that function and there wouldn't be any way to access it unless you use a closure.
What this does:
this.Impossible = "impossible!"
Is create a property definition on the this object. The value of this of course will change depending on the invocation of the surrounding function (ViewModel). In this case, you have called ViewModel() which means there's no object context and on scripts (non-strict mode) it will default to the global object (window).
Had you call it like this: new ViewModel() it would have created a new object and that would have been your this object which would have been assigned to your vm variable (and only accessible through it).
Firstly, $(document).ready(function (){}) apply global scope to the callback function function (){}. Try this, you will see console.log(this) is a Window object.
$(document).ready(function(){
console.log(this);// this is window (global) object
});
Secondly, this is different when calling function and creating an object instance of a function, i.e let vm = ViewModel(); vs let vm = new ViewModel();.
function f1 () {
function f2 () {
console.log(this === window);
}
f2()
}
f1()
In your case, by creating instance of ViewModel, you will see the different values of window.Impossible
$(document).ready(function(){
function ViewModel(){
this.Impossible = "impossible!";
}
let vm = new ViewModel(); /// create instance instead of calling
console.log('vm:' + vm.Impossible);
console.log("this: " + this.Impossible);
console.log("window: " + window.Impossible);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Javascript closure not working as expected

I'm running this code on nodejs. I was wondering why the closure when executed does not print the string 'Globals'? Isn't the this in the closure pointing to the global scope?
// Running on NodeJS, not in a browser!
this.name = "Globals";
function Person(name) {
this.name = name;
this.namePrinter = function() {
return function() {
console.log(this.name);
}
}
}
var p = new Person("Faiz");
p.namePrinter()(); // prints undefined. Shouldn't it print Globals?
console.log(this.name); // prints Globals
Your example works as intended in a browser, but in node.js this on the top level is not the same as global, it's your module .exports. So when you do
this.name = "Globals";
It assigns name: Globals to module.exports, not to the global object.
Now, when you write
p.namePrinter()();
it's the same as:
func = p.namePrinter();
func();
The function is unbound (= there's no object. before it), so its this will be the global object. But there's no name there...
In browsers, your top level code is executed in the context of the global object (which is window) and this is the same object unbound functions use. That's why your snippet works.

QUnit: How to pass a global variable reference?

I have a qunit module that doesn't allow a created object's method to see a global variable. The simplified version:
qunit.module("testcases", {
setup: function () {
var globalVar = "something";
}
});
test("test1", function () {
obj = new MyObj({});
obj.execute(); // uses globalVar but doesn't see it
});
How can I get the obj instance to recognize the globalVar?
I tried defining globalVar within test1 as well, but no luck. I don't want to put the globalVar on the obj instance, since it's execute function doesn't check on itself for the global
As mentioned by anonymous superuser -- cannot use var in the setup function because it's actually closed off as it's own scope

Get context of a function?

I'm using Jasmine to do unit tests and I am spying on a function:
it('should loop through all inputs', function() {
var test = function() {};
spyOn(this, 'test').andCallThrough();
formManager.eachInputs($unfoldedView, test);
expect(test).toHaveBeenCalled()
});
My problem is that the spyOn takes two parameters: (context, function). What is the context of the test function and how do I get it? It's context is the inside of this anonymous function, but I don't know how to get that. (I wrote this as the context parameter, but it's not that)
When you're in the global scope, your declared variables exist as members on the global object (either window or global). When you declare variables in a local function scope, there is no analogous "local" object. (See the Stack Overflow question "JavaScript: Reference a functions local scope as an object" for more details.)
Instead, you can make your function a method of an object and use that object as the context:
it('should loop through all inputs', function() {
var obj = { test: function() {} };
spyOn(obj, 'test').andCallThrough();
formManager.eachInputs($unfoldedView, obj.test);
expect(obj.test).toHaveBeenCalled()
});

"this" keyword in a javascript module

I have defined the following module globally:
var module = (function () {
console.log(this);
this.fn = function () {
console.log(this);
}
return this;
})();
http://www.quirksmode.org/js/this.html :
In JavaScript |this| always refers to the “owner” of the function we're executing, or rather, to the object that a function is a method of.
The first call to console.log logs Window as the value of this, and that I understand. But, so does also the second call to console.log.
Since this refers to the owner of the function, why does module.fn log Window and not module?
When I call fn I still have to write module.fn, I can't write Window.fn. Since this refers to Window I find this confusing.
EDIT: I forgot to return this in my example.
Since this refers to the owner of the function, why does module.fn log Window and not module?
The return value of the outer function is window because it doesn't get called in any particular context, so module ends up being window as well.
It seems that the way you have applied the module pattern is wrong. It should be returning the public interface that gets used in the rest of your code:
var module = (function () {
console.log(this);
// public interface
return {
fn: function () {
console.log(this);
}
}
})();
module.fn(); // "Object {fn: function}"
In your example, the global object receives the fn. It is the window object in case of browsers. That is because you are calling the function in place (effectively constructing a new scope) without specific context.
On the end, your module object is just a reference to the window (because of return this;).
What is this?
In JavaScript, this is the current context, the object on which the function was called that particular time. It is not the "holder" of the function. You can always "steal" the method from other objects and apply (literally) it to your own.
Assume you want to slice the arguments object for some reason. It looks just like an array, but it is NOT an array. arguments.slice(2,4) does not work (assuming ECMAScript < 5). What to do?
Array.prototype.slice.apply(arguments, [2,4]);
You need to steal the slice function from the Array prototype, and use if on your arguments. Inside the slice call, the "this" is the arguments object that particular time.
How to construct a valid module?
Your job is to return the module object. You do not want do mess with the context. It is not relevant, as long as you are not applying the function directly on module object.
The easiest solution is the simplest.
var module = (function() {
// do something internally, great for "private" stuff
// then return the "public" interface
return {
doSomething: function() {
// do something
},
introduce: function() {
console.log(this);
}
};
})();
module.introduce(); // Object {doSomething: function, introduce: function}
module.doSomething();
The other way.
Alternatively, you could use the this to do your job, using the apply, if you really want to.
var module = {};
(function(){
this.doSomething = function() {
// do something
};
this.introduce = function() {
console.log(this);
};
}).apply(module);
module.introduce(); // Object {doSomething: function, introduce: function}
module.doSomething();
Note this is almost equal to the "new" call.
There are more equally valid ways to do it, but the first presented one is frequently used and very clear. Anyway, everything really depends on your code conventions.
Your pattern is wrong what you are doing to make a closed scope and setting module to the return from that scope:
// This is the design pattern:
var module = (function () {
var module = {};
var localVar = 1;
module.moduleVar = 2;
module.fn = function () {
console.log(this);
}
return module;
})();
console.log(module.fn); // function() { console.log(this) ;}
console.log(module.moduleVar); // 2
console.log(module.localVar); // undefined

Categories