Javscript
When to use =>
The arrow function is very helpful to write consise code. They are called "Fat arrow". They are:
- Anonymous, basically it could be compared to a lambda
- They change the way
this
binds to functions
So with arrow functions, we do not have to type the function
and the return
keyword.
The fat arrow cannot blindly replace function declarations. Basically everytime you need to access this
of the current environement which is traditionally passed down with const self = this
you can replace it by an arrow function.
const obj = {
prop: 'test',
foo: function () {
const self = this
bar()
function bar() {
console.log(self)
}
}
}
obj.foo() // { prop: 'test', foo: [Function: foo] }
There are three JavaScript methods that can be use to bind the context call()
, apply()
and bind()
. They are quite similar. The difference is that unlike the two others bind()
is not called immediately but returns a closure.
Same result but with the fat arrow.
const obj = {
prop: 'test',
foo: function () {
const bar = () => {
console.log(this)
}
bar()
}
}
obj.foo()
// { prop: 'test', foo: [Function: foo] }
this
did behave like we expected. Additionnaly, arrow function cannot
- be called with
new
- be called with
bind()
orcall()
Arrow functions are only callable and class functions are only constructors while function expression are both.
We have seen that arrow function cannot use bind()
. When Vuejs creates a component instance, it uses bind()
behind the scene. Therefore we need to declare methods with function expressions:
methods: {
someFunction() {
// at the moment, `this` points to the `methods` object that the function is defined in.
},
someOtherFunction() {
this.someFunction(); // this should give an error
}
}
But when the component is instanciated, this
is bound to the component instance and not anymore to the methods object wich allows to call this.someFunction()
or this.nameOfAVariable()
The this Context
MDN In an object, a method will behave has we expect
In objects, this
var obj = {
foo: function() {
return this;
}
};
obj.foo() === obj; // true
On the other hand a function using this uses the global context:
function foo() {
console.log(this);
}
// window
But you can "instantiate" a class from a function, therefore using the above function with the new
keyword:
new foo()
// foo
Notice that this
is correctly bound to the newly created object. It works like creation of the object in the first example. Like in other language, the new
keyword calls a constructor. Therefore we could:
function Foo(bar) { // it's a class so we use uppercase
this.bar = bar
}
I can instantiate many objects:
const obj1 = new Foo('test');
const obj2 = new Foo('test2');
And if I call a method on obj1:
console.log(obj1.method())
// TypeError: obj1.method is not a function
Of course it crashes, we did not define it. Let's add it to the object
obj1.method = function () {
return 'method called';
}
obj1.method();
// method called
obj2.method();
// TypeError: obj2.method is not a function
The method does not exist on obj2
it was added to the instance of obj1
So we coud try to add it to Foo
our class
Foo.method2 = function() {
return 'method2 called',
}
obj1.method2();
obj2.method2();
// TypeError: obj1.method2 is not a function
// TypeError: obj2.method2 is not a function
Foo.method2();
// method2 called
So method 2 has been added to the instance the function object Foo
but it did not spread to the instatiated objects. This is because JavaScript is a language with prototypes. If we want to modifiy the Class we actually need to modify the prototype of the function.
So let's modify the prototype:
Foo.prototype.method3 = function() {
console.log(this);
return 'hello method 3';
}
Now both objects have a method available.
console.log(obj2.method3())
// Foo {}
// hello method 3
This in the context of method3()
would be exactly what we expect this would reference Foo
Members or properties of a Class work in the exact same manner.
Foo.prototype.color = 'black'
And both object would print black
if obj1.color
or obj2.color
was called.
But if I set a new color to obj1
:
obj1.color = 'white';
If we log it, it correctly prints white
and this happens:
console.log(obj1.__proto__.color) // black
console.log(obj2.__proto__.color) // black
console.log(obj1.color) // white
console.log(obj2.color) // black
But wait... did we not declare obj1 as constant? Yes we did... The linter should pick it up. But we will speak more on that.
We digressed slightly on prototypes but what we saw so far is that the this
keywords defaults to the global scope (window in the browser) when called as an unbound function.
This means that:
const obj3 = {
prop: 'test',
foo: function () {
bar()
function bar() {
console.log(this)
}
}
}
obj3.foo() // window
Yes, again the function bar()
is unbound, this means that this
refers to the window object. One of the method bind the context is to create a variable called by convention self
and pass it down as a closure.