Several Ways to call a function
cheatsheet1999 opened this issue · 0 comments
1. Regular function
this
is pointing to Window
function fn() {
console.log('This is a function');
}
function fn() {
console.log('This is a function' + this);
}
fn();
fn.call();
2. Method on objects
this
is pointing to object => o
because o
called sayHI
method
let o = {
sayHi: function() {
console.log('This is a function');
}
}
o.sayHi();
3. Constructor
this
is pointing to rick
because rick
instantiate the constructor
this
in the prototype object is also pointing to rick
function fn() {}
new fn();
let rick = new fn();
fn.prototype.start = function(){};
4. Register Event function
this
in register event function is pointing to the attached element, but in this example
let btn = document.querySelector('button');
btn.onclick = function() {
console.log(this)
}; //click the button to call the function
5. Timer function
this
is pointing to Window
setInterval(function() {}, 1000); //execute function in every second
6. IIFE (Immediately Invoked Function Expression)
this
is pointing to Window
(function() {
console.log('This is a function')
})();
Understanding of 'this' keyword
Scenario 1: 'this' in global environment
It is relatively straightforward in this scenario, the browser in the global environment invokes the function. 'this' is pointing to ** Window**. However, 'this' will point to undefined if 'use strict'.
function f1 () {
console.log(this)
}
function f2 () {
'use strict'
console.log(this)
}
f1() // window
f2() // undefined
There is a very similar question (tedious, useless, but interesting)
const foo = {
bar: 10,
fn: function() {
console.log(this)
console.log(this.bar)
}
}
var fn1 = foo.fn
fn1()
Output:
window
undefined
this
is still pointing to the Window. Although functionfn
is invoked by the objectfoo
(foo.fn), after assignment to fn1, there is a (). That means we execute fn1 in the global environment. Therefore, the code above still outputs window and undefined.
Now, if we change to
const foo = {
bar: 10,
fn: function() {
console.log(this)
console.log(this.bar)
}
}
foo.fn()
Output
{bar: 10, fn: ƒ}
10
this
is pointing to the object that invokes the function. Whenever a preceding dot calls a function, the object before that dot is this.- To be more clear, in
foo.fn()
,this
is pointing tofoo
. When executing function, ifthis
in the function is invoked by parent scope, thenthis
will point to parent scope environment, otherwise,this
point to global environment
Scenario 2: 'this' in the context environment
Let's dive into examples.
const person = {
name: 'Lucas',
brother: {
name: 'Mike',
fn: function() {
return this.name
}
}
}
console.log(person.brother.fn())
Mike
Why? Because in these nested relationships, this
will point to the LAST object that invokes the function. Here is the brother
scope, so we choose the name
variable that is in brother
scope.
Example 2:
const o1 = {
text: 'o1',
fn: function() {
return this.text
}
}
const o2 = {
text: 'o2',
fn: function() {
return o1.fn()
}
}
const o3 = {
text: 'o3',
fn: function() {
var fn = o1.fn
return fn()
}
}
console.log(o1.fn())
console.log(o2.fn())
console.log(o3.fn())
o1
o1
undefined
WHAT???
Let's dive into it:
- First, we got
o1
because ofo1.fn()
,o1
calledfn()
so this.text will print the variable that is insideo1
object. - Second, although it is the
o2
that invokedfn()
, inside the function of o2, it iso1.fn()
. Surprisingly, we jump to o1 and geto1
as its input - Last, after the
var fn
is assigned, it is a normalfn()
call, so here this points to the Window, and the answer is, of course, undefined.
A follow-up question, if we want o2
as output for
console.log(o2.fn())
without using call, apply, bind
What should we do?
const o1 = {
text: 'o1',
fn: function() {
return this.text
}
}
const o2 = {
text: 'o2',
fn: o1.fn
}
console.log(o2.fn())
We don't execute o1.fn
, instead, we only need the this.text
inside o1.fn
function. So in o2, we put this.text
on o2.fn
. Then execute it in the console.log(o2.fn())
Scenario 3: bind / call / apply
const foo = {
name: 'lucas',
logName: function() {
console.log(this.name)
}
}
const bar = {
name: 'mike'
}
console.log(foo.logName.call(bar))
The above code will output mike
Scenario 4: this and constructor
function Foo() {
this.bar = "Lucas"
}
const instance = new Foo()
console.log(instance.bar)
The output will be Lucas
, but what happens behind the scene after new
with constructor Foo
?
- Create a new Object
this
in constructor will point to the new object- Adding attributes, methods for the object
- return a new object
The above can be shown as code below
let obj = {}
obj.__proto__ = Foo.prototype
Foo.call(obj)
Here, we only simulate a naive version of new
Note, if return
has been mentioned in a constructor, there are two cases needed to be considered
First
function Foo(){
this.user = "Lucas"
const o = {}
return o
}
const instance = new Foo()
console.log(instance.user)
It will output undefined
because instance
is an empty object o
just returned
Second
function Foo(){
this.user = "Lucas"
return 1
}
const instance = new Foo()
console.log(instance.user)
It will output Lucas
, which means instance
is the object instantiating this
Conclusion: If a value is explicitly returned in the constructor and an object is returned, then this
points to the returned object; if the returned object is not an object, then this
still points to the instance.
1. During function precompilation, `this` points to `window`
2. In the global scope, `this` points to `window`
3. call/apply can change `this` pointing
4. obj.fn() who calls the method `this` points to whom
Scenario 5: New story, this
in the arrow function
this
for arrow functions does not apply to the above standard rules, but is determined according to the outer (function or global) context scope.
const foo = {
fn: function () {
setTimeout(function() {
console.log(this)
})
}
}
console.log(foo.fn())
In this question, this
is in an anonymous function of setTimeout()
, so this
is pointing to window
object. However, we can use arrow functions to make this
points to object foo
const foo = {
fn: function () {
setTimeout(() => {
console.log(this)
})
}
}
console.log(foo.fn())
Scenario 6: Prority of this
We often call the binding of this through call
, apply
, bind
, and new
as explicit binding; the pointing of this
determined according to the call relationship is called implicit binding.
But which one has a higher priority?
function foo (a) {
console.log(this.a)
}
const obj1 = {
a: 1,
foo: foo
}
const obj2 = {
a: 2,
foo: foo
}
obj1.foo.call(obj2)
obj2.foo.call(obj1)
Without call
function, the output will be 1
and 2
.
Now the output is 2
and 1
which means call
, apply
has a higher priority.
function foo (a) {
this.a = a
}
const obj1 = {}
var bar = foo.bind(obj1)
bar(2)
console.log(obj1.a)
By using bind
, this
in bar
has been binded on obj1
. After executing bar(2)
, obj1.a = 2
. Which is, after bar(2)
, obj1
is `{a: 2}
Turn to arrow function
function foo() {
return a => {
console.log(this.a)
};
}
const obj1 = {
a: 2
}
const obj2 = {
a: 3
}
const bar = foo.call(obj1)
console.log(bar.call(obj2))
The output will be 2. We first bind this
in foo()
on obj1
. The this
of bar
(reference arrow function) will also be bound to obj1
, and the arrow function binding cannot be modified. Note here, foo() is not an arrow function, that's why we can use call
function on foo.
If we refactor foo
to arrow function completely
var a = 123
const foo = () => a => {
console.log(this.a)
}
const obj1 = {
a: 2
}
const obj2 = {
a: 3
}
var bar = foo.call(obj1)
console.log(bar.call(obj2))
It will output 123
.
Reference
https://www.zhihu.com/question/353757734/answer/964557747
https://blog.kevinchisholm.com/javascript/context-object-literals/