I'm going through Javascript 30 by Wes Bos.
I'm doing the AJAX type ahead course, and I'm sort of trying to limit the amount of ES6 I do on it, just for the sake of practice.
This is the finished version:
Note that as you type in a location, the searched value gets highlighted in yellow within the results.
Now the javascript that determines this is the displayMatches function:
var displayMatches = function(){
console.log(this.value);
var matchArray = findMatches(this.value, cities);
var html = matchArray.map(function(place){
console.log(this, place, "test");
var regex = new RegExp(this.value, 'gi');
var cityName = place.city.replace(regex, "<span class='hl'>" + this.value + "</span>");
var stateName = place.state.replace(regex, "<span class='hl'>" + this.value + "</span>");
return `
<li>
<span class="name">${cityName}, ${stateName}</span>
<span class="population">${place.population}</span>
</li>
`;
}).join("");
suggestions.innerHTML = html;
}
The issue lies with the function variable html
. Currently I have it as:
var html = matchArray.map(function(place){...})
This doesn't work, and I will get undefined in the results.
However, if I change it into an arrow function:
var html = matchArray.map(place => {...})
The function will run and the searched value will get highlighted.
Can someone explain why within this particular context, the arrow function works?
Thanks in advance!
I'm going through Javascript 30 by Wes Bos.
I'm doing the AJAX type ahead course, and I'm sort of trying to limit the amount of ES6 I do on it, just for the sake of practice.
This is the finished version:
https://codepen.io/Airster/pen/KNYXYN
Note that as you type in a location, the searched value gets highlighted in yellow within the results.
Now the javascript that determines this is the displayMatches function:
var displayMatches = function(){
console.log(this.value);
var matchArray = findMatches(this.value, cities);
var html = matchArray.map(function(place){
console.log(this, place, "test");
var regex = new RegExp(this.value, 'gi');
var cityName = place.city.replace(regex, "<span class='hl'>" + this.value + "</span>");
var stateName = place.state.replace(regex, "<span class='hl'>" + this.value + "</span>");
return `
<li>
<span class="name">${cityName}, ${stateName}</span>
<span class="population">${place.population}</span>
</li>
`;
}).join("");
suggestions.innerHTML = html;
}
The issue lies with the function variable html
. Currently I have it as:
var html = matchArray.map(function(place){...})
This doesn't work, and I will get undefined in the results.
However, if I change it into an arrow function:
var html = matchArray.map(place => {...})
The function will run and the searched value will get highlighted.
Can someone explain why within this particular context, the arrow function works?
Thanks in advance!
this.value
is only used, consider if you couldn't make it an argument of the method, which would make a lot more sense
– Icepickle
Commented
Oct 9, 2017 at 8:40
Can someone explain why within this particular context, the arrow function works?
An arrow function (by definition) preserves the current lexical value of this
. A regular callback function does not. So, when you use the regular callback function, you lose the proper value of this
which your code depends on.
You can fix that by either using an arrow function which preserves the current scope value of this
or by passing the optional thisArg
to .map()
as an argument right after the function.
From the MDN doc for Array.prototype.map
, you can see the optional thisArg
:
var new_array = arr.map(function callback(currentValue, index, array) { // Return element for new_array }[, thisArg])
So, to make your code preserve this
without an arrow function, you can pass the thisArg
to you call to .map()
.
var displayMatches = function(){
console.log(this.value);
var matchArray = findMatches(this.value, cities);
var html = matchArray.map(function(place){
console.log(this, place, "test");
var regex = new RegExp(this.value, 'gi');
var cityName = place.city.replace(regex, "<span class='hl'>" + this.value + "</span>");
var stateName = place.state.replace(regex, "<span class='hl'>" + this.value + "</span>");
return `
<li>
<span class="name">${cityName}, ${stateName}</span>
<span class="population">${place.population}</span>
</li>
`;
}, this).join(""); // <== See the this argument passed here
suggestions.innerHTML = html;
}
FYI, for pleteness, there are actually six possible solutions here:
this
argument to .map()
as shown above.bind(this)
with the callback functionvar self = this;
and reference self
instead of this
.let val = this.value
before the callback and then just use val
inside the callback (code shown below).e
parameter to the event handler and use e.target.value
instead of this.value
, removing the need to use this
at all.Since .map()
has the desired feature for this
built-in, if you're not going to use an ES6 arrow function, then it makes the most sense to me to use the .map()
argument, but all of these four solutions will work.
Looking at the details of what you're doing with this
in your callback, you are only reading this.value
so you could also just grab let val = this.val
before the callback and just use val
inside the callback too - eliminating the need to even use this
at all.
var displayMatches = function(){
let val = this.value;
console.log(val);
var matchArray = findMatches(val, cities);
var html = matchArray.map(function(place){
console.log(this, place, "test");
var regex = new RegExp(val, 'gi');
var cityName = place.city.replace(regex, "<span class='hl'>" + val + "</span>");
var stateName = place.state.replace(regex, "<span class='hl'>" + val + "</span>");
return `
<li>
<span class="name">${cityName}, ${stateName}</span>
<span class="population">${place.population}</span>
</li>
`;
}).join("");
suggestions.innerHTML = html;
}
The problem is that unlike in an arrow function the this
variable is being renewed for the new function. You should store the value of this
in a new variable so you don't lose it in the function.
var displayMatches = function(){
console.log(this.value);
var that = this;
var matchArray = findMatches(this.value, cities);
var html = matchArray.map(function(place){
console.log(that, place, "test");
var regex = new RegExp(that.value, 'gi');
var cityName = place.city.replace(regex, "<span class='hl'>" + that.value + "</span>");
var stateName = place.state.replace(regex, "<span class='hl'>" + that.value + "</span>");
return `
<li>
<span class="name">${cityName}, ${stateName}</span>
<span class="population">${place.population}</span>
</li>
`;
}).join("");
suggestions.innerHTML = html;
}
It is the use of this
variable. When you are using this inside a function, it will start to refer to the function itself rather than the one you intend it to.
Saving the value to another variable before use will do the trick for you.
const value = this.value;
const html = matchArray.map(function(place) {
const regex = new RegExp(value, 'gi');
...
});
In JavaScript, functions are first-class objects, because they can have properties and methods just like any other object. As a side effect of this, using the this
variable inside a function (anonymous or otherwise) will result in it referring to itself rather than the class object which is mon in Object Oriented Programming.
https://developer.mozilla/en-US/docs/Web/JavaScript/Guide/Functions#Lexical_this
Arrow functions do not bind this
variable.
An arrow function does not create its own this, the this value of the enclosing execution context is used.
Here is how that will work out in your codepen. https://codepen.io/BoyWithSilverWings/pen/VMXyXB?editors=0010