Re-Realizing context
Often when getting started into programming most seasonal developers struggle their way into understanding this
(especially in functional languages) and get into all sort of troubles that even further messes up things, however โthisโ becomes easy when they understand that this
is all about the context of invoking a function rather than confusing yourself with from where it was declared(which is mostly the case in Object Oriented language).
It ainโt what you donโt know that gets you into trouble, Its what you know for sure that just ainโt so -Mark Twain
If you find above terms difficult not to worry they are themselves a fully fledged topics to write an article or even a book to get into nitty-gritty details of this
Implicit Binding
Implicit binding is when the context of invoking this
is internally, intentionally and automatically bound to something else. These are considered as default standards/rules of how this
behaves, for example
this
with a getter or setter is one such example of implicit binding, consider another example below
function greet() {// this === userDetails //trueconsole.log(`Hello ${this.fullName}!`) //Hello Jay Gurav!}const userDetails = {fullName: "Jay Gurav",greet,}userDetails.greet()
When a function is called as a method of an object, its this is set to the object the method is called on. So in the above example greet
method on object userDetails
has this
internally bounded to the object which invokes the function as its method using .
operator, so the this === userDetails
would result to true in greet
function when called using userDetails.greet()
. It to important to note that only the last property matters, so something like below
function greet() {// this === userDetails //falseconsole.log(`Hello ${this.fullName}!`) //Hello John Doe!}const userDetails = {fullName: "Jay Gurav",greet,}const dummyUserDetails = {fullName: "John Doe",greet: userDetails.greet,}dummyUserDetails.greet()
Here even if the greet property on dummyUserDetails
points to userDetails.greet
the this
here in this case would be dummyUserDetails
and not userDetails
. So always remember in case of objects this
refers to the object on which the method is invoked.
Explicit binding
so as the title says to re-realize context or to explicitly set this
to some another context of our wish, JavaScript provides us with three useful methods .bind()
, .call()
and .apply()
available to all functions through Function.prototype
object to do so.
So we can achieve something that we intended to do above as follows
function greet() {console.log(this === userDetails) //trueconsole.log(`Hello ${this.fullName}!`) //Hello Jay Gurav!}const userDetails = {fullName: "Jay Gurav",greet,}const dummyUserDetails = {fullName: "John Doe",greet: greet.bind(userDetails),}dummyUserDetails.greet()
Here we explicitly say that we want to bind the method (not function) greet
on dummyUserDetails
with the context of userDetails
.
call
The call
method on Function.prototype
object call a function with the provided context assigned to this, and the following arguments passed to the function as its arguments list.
Function.prototype.call ( thisArg, ...args )
The thisArg
value is passed without modification as this
value to the calling function. However it is important to note that when undefined
or null
is passed as thisArg
then it is replaced with the global object and if the function is an arrow function then the thisArg
will be ignored by the function.
for example
function log(ps) {console.log(`${ps} ${this.name}`)}const product = {name: "cheese",}const myPet = {name: "Bruno",}log.call(product, "Yummy") //Yummy cheese.log.call(myPet, "๐ถ") // ๐ถ Bruno
apply
The apply
method on Function.prototype
object calls a function with the provided context assigned to this, and the array/array-like-object passed as the second argument that is passed to the calling function.
Function.prototype.apply ( thisArg, argArray )
Note that the syntax of
.call()
and.apply
is almost identical , the fundamental difference is that.call()
accepts an argument list while.apply
accepts a single array of argument.
so in the above example we could be doing something like log.call(myPet, ["๐ถ", "๐ฐ"])
bind
The .bind()
method on Function.prototype
object creates a new function that when call has its this
set to the provided context and the following arguments passed to the function as its arguments list.
Function.prototype.bind ( thisArg, ...args )
consider this below example
const user = {name: "John Doe",age: 42,getUser: function (){return {name: this.name, age: this.age};}}const globalContextUser = user.getUser;getUser() //{ name: undefined, age: undefined } as it is invoked from global scopeconst boundedUser.bind(user); //this is set to user, returns a new functionboundedUser() //{ name: "John Doe", age: 42 }
๐ Further reading and resources
- Ecma specification
- MDN documentation on .apply(), .call(), .bind()