-
Notifications
You must be signed in to change notification settings - Fork 333
Description
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 function5. Timer function
this is pointing to Window
setInterval(function() {}, 1000); //execute function in every second6. 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() // undefinedThere 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
thisis still pointing to the Window. Although functionfnis 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
thisis 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(),thisis pointing tofoo. When executing function, ifthisin the function is invoked by parent scope, thenthiswill point to parent scope environment, otherwise,thispoint 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
o1because ofo1.fn(),o1calledfn()so this.text will print the variable that is insideo1object. - Second, although it is the
o2that invokedfn(), inside the function of o2, it iso1.fn(). Surprisingly, we jump to o1 and geto1as its input - Last, after the
var fnis 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
thisin 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/