I have a class that is structured something like this
function myClass () {
this.varA = 0;
}
myClass.prototype.consts = {
someConst : 1,
anotherConst : 1000,
}
myClass.prototype.startIt = function () {
window.setTimeout(this.brokenFunc.bind(this), this.consts.anotherConst);
}
myClass.prototype.brokenFunc = function () {
this.varA += this.consts.someConst;
window.setTimeout(this.brokenFunc.bind(this), this.consts.anotherConst);
}
// Example call
var myObj = new myClass();
myObj.startIt();
This works fine on most Android devices -- but a user running Android 2.3 has now informed me that it doesn't work and I could reproduce the error in an emulator. First, it says TypeError: Result of expression 'this.brokenFunc.bind' [undefined] is not a function for this line (within startIt):
window.setTimeout(this.brokenFunc.bind(this), this.consts.anotherConst);
Fair enough, I thought, and did the old var _this = this trick to get around the bind call. But now it says TypeError: Result of expression 'this.consts' [undefined] is not an object in this line
this.varA += this.consts.someConst;
And I'm a little lost. How can this piece of code not work? Especially since it works in most Android versions.
By default, setTimeout invokes functions with this of the global object (i.e., window, in the browser). (Side note: in strict mode, it's undefined instead.) Thus, when you don't use bind, the this brokenFunc is now window instead of the object that registered the timeout.
In order to preserve the this from startIt, you'll need to wrap your call in an anonymous function:
myClass.prototype.startIt = function () {
var that = this;
window.setTimeout(function() {
// call brokenFunc with the outer this
that.brokenFunc();
}, this.consts.anotherConst);
}
Your first error happened because the Android 2.3 browser doesn't support the EMCAScript 5 bind function. Your second error appeared because this.consts is really window.consts, which doesn't exist. Just wrap the call in an anonymous function that has that in its scope, and call the function from that.
Related
I have JavaScript Closure methods I need to convert in to TypeScript. I am not able to do that. Please help me. How to write a function inside
a function in TypeScript?
var postPonedMethod1 = function() {
var postPonedMethod2 = function() {
this.getClaimNotices();
this.gridServices.get();
//$scope.gridServicesReciepts.get();
$scope.setDataLoading(false);
};
postPonedMethod2();
}
Your problem is the use of this: postPonedMethod2 refers to this, but it isn't a function defined on a class or object. If you're using popular TypeScript options --noImplicitThis or --strict, TypeScript will complain, because it is possible that you will eventually call postPonedMethod2 in a way that does not provide the expected this instance. In fact, in JavaScript's "strict mode" ("use strict";) you might find that this is undefined where it wasn't before.
In strict mode, however, if the value of this is not set when entering an execution context, it remains as undefined, as shown in the following example:
function f2() {
'use strict'; // see strict mode
return this;
}
f2() === undefined; // true
If possible, I'd switch to defining your AngularJS component as a class, calling this.postPonedMethod1() and this.postPonedMethod2() for clarity.
In general in TypeScript, the solution is to type the "this" outside a class is to define an argument called this as your function's first parameter, which tells TypeScript what type to expect. To temporarily get through the problem, though, you can explicitly set this: any. This defeats the purpose of TypeScript here, because any provides no type checking at all, but it would allow you to solve the problem later in a different commit.
That fixes the typing, but you'll still need to ensure that the value of this is set correctly where you call postPonedMethod2. This would mean one of:
Using an arrow function () => { instead of function () { for postPonedMethod2. An arrow function explicitly does not redefine this.
Calling bind(this) where postPonedMethod2 is defined, or using call as in postPonedMethod2.call(this) where you call it.
Avoiding "use strict" with --noImplicitUseStrict, if you're otherwise trying to emit a module.
Saving the outer value of this to a place where it won't be redefined, as I show below.
var postPonedMethod1 = function() {
var postPonedMethod2 = function() {
this.getClaimNotices(); // error: 'this' implicitly has type 'any' because it does not have a type annotation.
this.gridServices.get(); // error: 'this' implicitly has type 'any' because it does not have a type annotation.
//$scope.gridServicesReciepts.get();
$scope.setDataLoading(false);
};
postPonedMethod2();
}
var fixedPostPonedMethod1 = function(this: any) { // Do better than "any" if you can.
var component = this; // Store the enclosing "this".
var postPonedMethod2 = function() {
component.getClaimNotices();
component.gridServices.get();
//$scope.gridServicesReciepts.get();
$scope.setDataLoading(false);
};
postPonedMethod2(); // You could also call "bind".
}
It works for me like below
var postPonedMethod1 = () => {
var postPonedMethod2 = () => {
this.getClaimNotices();
this.gridServices.get();
//$scope.gridServicesReciepts.get();
$scope.setDataLoading(false);
};
postPonedMethod2();
}
I have a GeneralWrapper object that calls the statically-defined functions in the Library1 and Library2 objects.
The aim is that by calling GeneralWrapper.someFunc(), this will also call Library1.someFunc() and Library2.someFunc() without me having to explicitly create a function in GeneralWrapper called someFunc.
I attempt to implement this in the __preamble method below:
var GeneralWrapper = {
__modespopulated: false,
__validmodes: { // All 3 of these
spoonFunc: 1, // functions exist
knifeFunc: 1, // in Library1 and
forkFunc: 1 // Library2
},
__switchMode: function(funcname){
if (funcname in GeneralWrapper.__validmodes){
console.log("calling function", funcname)
GeneralWrapper.__preamble()
Library1[ funcname ](); // Call mode in Library1
Library2[ funcname ](); // Call mode in Library2
}
},
/* Attach valid modes to General Wrapper at runtime */
__preamble: function(){
if (!GeneralWrapper.__modespopulated)
{
for (var mode in GeneralWrapper.__validmodes)
{
GeneralWrapper[mode] = function(){
GeneralWrapper.__switchMode(mode)
};
}
GeneralWrapper.__modespopulated = true
}
GeneralWrapper.__otherprestuff();
},
__otherprestuff: function(){
// Stuff
},
funcThatAlwaysGetsCalled: function(){
GeneralWrapper.__switchMode("forkFunc");
}
}
var Library1 = {
forkFunc(){console.log("Lib1","fork")},
spoonFunc(){console.log("Lib1","spoon")},
knifeFunc(){console.log("Lib1","knife")}
}
var Library2 = {
forkFunc(){console.log("Lib2","FORK")},
spoonFunc(){console.log("Lib2","SPOON")},
knifeFunc(){console.log("Lib2","KNIFE")}
}
// Okay, let's initialise the object
GeneralWrapper.funcThatAlwaysGetsCalled();
For some reason calls to GeneralWrapper.spoonFunc() and GeneralWrapper.knifeFunc() always defer to the Fork output.
I imagine the problem stems from the anonymous function assignment on the GeneralWrapper[mode] = blah line where JS treats it as the same function each time, but I don't know how to get around this.
Please advise.
Solution:
Change this line:
for (var mode in GeneralWrapper.__validmodes)
into this:
for (let mode in GeneralWrapper.__validmodes)
Explanation:
what happens in your code (when binding functions in __preamble's loop) is that you create an anonymous function, which is totally fine. The problem is, your anon function has received the mode as a reference to local variable, so it's value is not automatically cloned but rather accessed at runtime. The main problem is that you've used var keyword, which means "hoisted variable" (it gets declared at the top of the function it was defined inside, even if it's somewhere in the middle of your function's code). In this scenario, you need a "block-scoped" variable, which will be bound to each loop iteration separately.
You can read more about variables hostings on MDN:
var at MDN
let at MDN
One thing you have to know - let was introduced in ES2015, so if you worry about backward compatibility with older browsers, you either have to use Function.prototype.bind or IIFE
One potential problem here is that you're creating functions inside a loop which can lead to some performance problems or unexpected behavior.
I'd replace:
for (var mode in GeneralWrapper.__validmodes)
{
GeneralWrapper[mode] = function(){
GeneralWrapper.__switchMode(mode)
};
}
with:
for (var mode in GeneralWrapper.__validmodes)
{
GeneralWrapper[mode] = GeneralWrapper.__switchMode.bind(this, mode);
}
Which should solve the problem at hand.
Context
I have a piece of code that runs through karma with jasmine and phantomjs as a browser, and my problem is that I can't figure out why the delete keyword does not do its job. I looked on phantomjs github to find a hint or a clear documentation about keywords support, in vain.
UPDATE
It actually seems it does not work on chrome either now...
here is the output of the expectation
ShopDataServiceTest should not have an instance method remove FAILED
Expected { getModelName : Function } not to have method 'remove'.
Here is my tested code:
// CRUDService basically returns a new class
// with a prototype containing a method remove
ShopDataService = CRUDService.build(modelName);
delete ShopDataService.prototype.remove;
Here the code that is executed by karma
expect(ShopDataService.prototype).not.toHaveMethod('remove');
Important note
This code is running under karma and does not work in that case, but it works when running on chrome, am I missing something here ?
EDIT I did not mention it before, the expression typeof ShopDataService.prototype.remove returns 'function', as ppoliani pointed out.
Well that's the awkward moment when you realize you prototyped wrong !
Why I was wrong
ShopDataService simply does not have its own property remove, since it is inherited from CRUD which has itself a method remove in its prototype.
I had this
var CRUD = function CRUD(modelName) {
this.getModelName = function () {
return modelName;
};
};
CRUD.prototype = {
save: function () {
// ABSTRACT
},
/**
* Deletes instance from id property
* #return http promise
*/
remove: function () {
// call api
}
};
And now it works with this
var CRUD = function CRUD(modelName) {
this.getModelName = function () {
return modelName;
};
this.save = function () {};
this.remove = function () {};
};
As a side effect, I should not hit the CRUD prototype to define the remove method, but instead define it on the newly created prototype extending CRUD.
If you're running your code in 'strict mode'; it might not allow you to use the delete operator to full extent.
I have an issuer where I lose the this inside this object. The output of the following piece of JavaScript gives me "some-id" and then undefined. When I use this inside a callback function, the scope goes out of the object and it cannot use this any more. How can I get the callback to use 'this' or at least have access to the object?
Since I will make multiple objects, I won't be able to create a 'static' like storage.
Here is my test code that you can use to reproduce my problem. What I would like to have is CheckBox.doSomething() to return the value of this.id which should match some-id for this test case.
function CheckBox(input_id) {
this.id = input_id;
this.doSomething();
$('#some-element').click(this.doSomething);
}
Checkbox.prototype.doSomething = function() {
alert(this.input_id);
}
var some_box = new CheckBox('some-id');
some_box.doSomething();
$('#some-element').click();
I can't even get this to work as I want it to:
function CheckBox2(input_id) {
this.id = input_id;
alert(this.id);
}
CheckBox2.prototype.doSomething = function() {
alert(this.input_id);
}
var some_box = new CheckBox2('some-id');
some_box.doSomething();
Your problem is with this line: $('#some-element').click(this.doSomething);
Why this is a problem
JavaScript methods don't know anything about the object that should be assigned to this, it's set when the method is called either explicitly (with myFunction.call(obj)) or implicitly (when called using obj.myFunction()).
For example:
var x = {
logThis: function () {
console.log(this);
}
};
x.logThis(); // logs x
x.logThis.call(y); // logs y
var func = x.logThis;
func(); // logs window: the fallback for when no value is given for `this`
In your case, you're passing this.doSomething to jQuery, which is then explicitly calling it with the element that was clicked as the value of this. What's happening is (a slightly more complex version of) this:
var callback = this.doSomething;
callback.call(anElement, anEvent);
The solution
You need to make sure that doSomething is called with the right value of this. You can do that by wrapping it in another function:
var cb = this;
$('#some-element').click(function() {
return cb.doSomething();
});
jQuery provides a proxy function lets you do this more simply:
$('#some-element').click(jQuery.proxy(this.doSomething, this));
function CheckBox(input_id) {
this.id = input_id;
this.doSomething = $.proxy( this.doSomething, this );
$('#some-element').click(this.doSomething);
}
The "javascript equivalent" of this is Function#bind but that is not available in every browser and since it seems you are using jQuery I am using the jQuery equivalent $.proxy
Others have already explained the causes of the problem and how to fix it with jQuery. What's left is how you fix it with standard JavaScript. Instead of ...
$('#some-element').click(this.doSomething);
... you write:
document.getElementById('some-element').addEventListener('click', this.doSomething.bind(this));
This changes the context of this inside doSomething. You can also do that with anonymous functions - instead of ...
$('#some-element').click(function(event) {
console.log(this);
});
... you write:
document.getElementById('#some-element').addEventListener('click', (function(event) {
console.log(this);
}).bind(this));
That has been very useful to me in projects with lots of callbacks, e.g. in Node.js (where you don't have to care about outdated browsers).
Edit: getElementById() and addEventListener() instead of $(...).click(...).
How does JQuery handle the "Cannot set property xxx of undefined" issue?" If I have:
function Foo() {
Foo.fn = Foo.prototype;
}
Then I have an extension JavaScript file with this code:
(function(foo) {
foo.fn.myPlugin = function() {
//return something here
};
})(Foo);
I get an error that says "Cannot set property myPlugin of undefined." Funny enough if I just copy the plugin code into FireBug or Chrome and execute it in the console, it works fine and attaches and works as expected like:
foo.myPlugin();
I'm guessing my problem either comes from the timing of the browser loading the document, or the fact that my existing object isn't inside of it's own namespace. I'm wondering if this only works when you define a class using the var syntax??
I don't know...from what I understand...either should work.
function Baz() {
return this;
}
Baz.prototype.fn = Baz.prototype;
var Foo = new Baz();
Baz = null;
(function(Bar) {
Bar.fn.myPlugin = function() {
return "test";
};
})(Foo);
Foo.myPlugin(); //returns test
This works, and leaves Baz assignable. Not sure if that is how jQuery does it or not.
From what I can tell, you never execute the line:
Foo.fn = Foo.prototype;
in your Foo() function.
Functions only execute when you call them.
Try using the singleton pattern:
var Foo = new function Foo() {
this.fn = this.prototype;
};
In this case, you're creating an instance of the Foo object by calling the new operator on the named anonymous Foo() function.
This isn't an issue for jQuery. There's at least one problem in your code, as pointed out in another answer.
If you are serious about understanding this, John Resig (the original author of jQuery) has put together an excellent interactive tutorial called Learning Advanced Javascript.