I'm relatively new to JS world.
I am used to write UI with QT (great tools to build UI!). With the QT I'm doing a class for each element: If I have a table with some elements I have a class for each element and a class for the table (maybe also for the rows). Each class contains the data and the method for manipulating its "DOM".
Now in html I'm using some ejs files that contains an empty skeleton on the divs and I'm writing a class for manipulating it via jquery.
For example:
userslist.ejs:
<script type="text/javascript">
function UsersList(path) {
this.path = path;
$('#userList > tbody').empty();
this.loadAjax();
}
UsersList.prototype.loadAjax = function() {
var that = this;
$.getJSON(this.path, function(users) {
that.fillUsers(users);
});
};
UsersList.prototype.fillUsers = function(users) {
var that = this;
users.forEach(function(user){
var tr = that.buildTr(user);
$('#userList > tbody:last').append(tr);
});
};
UsersList.prototype.buildTr = function(user) {
...
};
</script>
<h1>Users list</h1>
<table id="userList">
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Control</th>
</tr>
</thead>
<tbody></tbody>
</table>
With callback way of coding in JavaScript (and jquery dom manipulation in general) I easily lost the this
keyword to refer to class methods and class property (even if I earn some control in the nested callback). And I'm find myself to really abuse of the var that = this;
thing.. I have it in almost each method in the class.
How can I avoid this? I like OOP but I'm curious to explore this new trendy js, with some functional feature and with the asynch everywhere.
What is the correct way to handle this problem in JS? How can I avoid to write var that = this;
in each method every time I use a callback?
Very often when you use async programming in javascript (events, ajax) the context is the context of the event so you lose the context of your object.
the var that = this
pattern, that you will even more often read as var self = this
or var $this = this
allow to add a closure in the methods in order to re-establish the context.
here is another method that will force the context to be this inside the object method when it will be called :
UsersList.prototype.loadAjax = function() {
var that = this;
$.getJSON(this.path, this.fillUsers.bind(this));
};
You can use .bind
to preserve context. Newer versions of JavaScript (ES6) resolve this with new arrow notation. Moreover, some functions accept a context parameter.
Here is the ES5 solution:
UsersList.prototype.fillUsers = function(users) {
users.forEach(function(user){
var tr = this.buildTr(user);
$('#userList > tbody:last').append(tr);
}, this); // note the context parameter
};
Here is the ES6 solution:
UsersList.prototype.fillUsers = function(users) {
users.forEach((user) => { // arrow notation
var tr = this.buildTr(user);
$('#userList > tbody:last').append(tr);
});
};
Of course, the ES3 way would have been to use a normal for loop rather than a forEach.
As for $.getJSON
jQuery's $.ajax
also accepts a context parameter:
$.ajax({
url: this.path,
context: this
}).done(function(users) {
this.fillUsers(users);
});
Or, with ES6 arrows:
$.getJSON(this.path).done((users) => this.fillUsers(users));
As you might have found out, "classical" oop doesn't look too great in JavaScript like that, and you might want to distribute where code is differently than you would in a classical language.
You could create a decorator that passes the context as an argument (ala Python), so you can access it within nested scopes. And also, you could return this
for chaining:
function fluent(f) {
return function() {
f.apply(this, [this].concat([].slice.call(arguments)))
return this
}
}
function Ary(xs) {
this.xs = xs
}
Ary.prototype.method = fluent(function(self, a, b) {
self.xs.forEach(function(x) {
console.log(self.xs, x + a + b)
})
})
var a = new Ary([1,2,3])
a.method(1, 2)
//[1, 2, 3] 4
//[1, 2, 3] 5
//[1, 2, 3] 6
One alternative to the other answers is to use jQuery's .proxy
. An example:
UsersList.prototype.loadAjax = function() {
$.getJSON(this.path, $.proxy(function(users) {
//jQuery.proxy ensures that 'this' referring to the object you intend to refer to, i.e. the object passed in to the second argument of .proxy
this.fillUsers(users);
}), this); //Note we pass 'this' to .proxy
};
I think this similar question helps to illustrate: $(this) inside of AJAX success not working.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With