Disclaimer
There are 1000000 articles about this in JS. I think it is perfectly fine to add one more.
Second parameter of Array.prototype.map
Recently I have checked the MDN page about .map() method of the array prototype and met one paragraph witch hooked my attention:
where thisArg is a second parameter of .map() method (1)
The second sentence says - "Otherwise, the value undefined will be used as its this value." So, let's do a quick test and pass inside .map() only a callback:
[1,2,3].map(function(){console.log(this)})
// globalObject
// globalObject
// globalObject
It seems confusing as this value is a global object and not undefined when a callback is executing. Actually it is expected as the callback is executed in non-strict mode. Why then the phrase says undefined?
Nevertheless, the phrase makes sense, especially, if we pay attention to the line - "The this value ultimately observable by callbackFn".
So, what "ultimately observable" does mean?
Default value of function's this
Let's create a simplest function ever and try to call it by .apply() method. The .apply() method allows us to set this value explicitly and let's set this as undefined
function f (){
console.log(this)
}
f.apply(undefined)
// globalObject
So, it is possible to conclude that function f ultimately observes globalObject value in this and not undefined value we explicitly pass. In my opinion we may compare such behaviour with default function parameters:
function f(a=globalObject){
console.log(a)
}
f();
Spec time!
Now it is time to open the ES specification just to be sure that we do not miss any cases.
I really would like to make your life easier, so there is an abstract operation OrdinaryCallBindThis which is responsible for default this value. There are several steps but 2 of them are main:
Let's pay attention to 2 variables on the pic:
- thisArgument - is a passed value in a function;
- thisValue - is an ultimately observable value by a function;
Step 5 says that if our function is called in strict mode then thisValue becomes thisArgument and default value for this is not applicable.
function f (){
'use strict'
console.log(this)
}
f.apply(undefined)
// undefined
Step 6 says that if our function is called in non-strict mode and thisArgument equals to undefined or null then thisValue (actual value of this inside function) is globalObject (globalEnv.[[GlobalThisValue]] on the pic is just a global object).
function f (){
console.log(this)
}
f.apply(undefined)
f.apply(null)
// globalObject
// globalObject
So null value is also substitute with globalObject default value.
Also it is worth to have a look at 6.b which is also valid for non-strict mode. There is call of toObject() operation which creates wrapper objects (String, Number, etc) for primitive values of thisArgument:
function f (){
console.log(this)
}
f.apply('')
// String {''}
Therefore function's this value is always an object type for non-strict mode.
Conclusion
Each time you call a function in JS you pass a this value in it. Even if you call a function like f(); you still implicitly pass undefined value. A strict mode function accepts any this value without any modifications. A non-strict function substitutes undefined and null values with global object and also transform all primitive values in objects.
Feel free to use that conclusion to win a heart of your next interviewer.
P.S.
There is one my old post about this value in a setTimeout callback. In a browser callback's this is always window and it does not depend on strict/non-strict mode. The reason is that setTimeout method passes window object not undefined as this when executes a callback. Non-strict default substitution just does not work as thisArgument is neither undefined nor null.
(1) - Array.prototype.map() method has a second optional parameter:
// Callback function
map(callbackFn)
map(callbackFn, thisArg)
that second parameter may be used instead of .bind() method for a callback function.
Top comments (3)
Love that kind of posts! 😁
It's useful to dig deep into core API features
Thanks=) Appreciate it!
Wow, did not know about .apply(undefined)!
Cool stuff!