[Medium] π this Binding
1. What is this in JavaScript?β
What is
thisin JavaScript?
this is a keyword in JavaScript that points to the execution context object of a function.
The value of this depends on how the function is called, not where it is defined.
this binding rulesβ
There are four binding rules for this in JavaScript (highest to lowest priority):
- new binding: function called with
new - explicit binding:
call,apply, orbindexplicitly setsthis - implicit binding: called as an object method
- default binding: fallback behavior in other call sites
2. Please explain the difference of this in different contextsβ
Explain how
thisbehaves in different contexts.
1. this in global contextβ
console.log(this); // browser: window, Node.js: global
function globalFunction() {
console.log(this); // non-strict: window/global, strict: undefined
}
globalFunction();
'use strict';
function strictFunction() {
console.log(this); // undefined
}
strictFunction();
2. this in regular functionsβ
For regular functions, this depends on call site:
function regularFunction() {
console.log(this);
}
// direct call
regularFunction(); // window (non-strict) or undefined (strict)
// method call
const obj = {
method: regularFunction,
};
obj.method(); // obj
// call/apply/bind
const customObj = { name: 'Custom' };
regularFunction.call(customObj); // customObj
3. this in arrow functionsβ
Arrow functions do not have their own this.
They inherit this from outer lexical scope.
const obj = {
name: 'Object',
// regular method
regularMethod: function () {
console.log('regularMethod this:', this); // obj
// inner regular function: this changes
function innerRegular() {
console.log('innerRegular this:', this); // window/undefined
}
innerRegular();
// inner arrow function: this is inherited
const innerArrow = () => {
console.log('innerArrow this:', this); // obj
};
innerArrow();
},
// arrow function as object property
arrowMethod: () => {
console.log('arrowMethod this:', this); // window/global lexical scope
},
};
obj.regularMethod();
obj.arrowMethod();
4. this in object methodsβ
const person = {
name: 'John',
age: 30,
// regular function: this -> person
greet: function () {
console.log(`Hello, I'm ${this.name}`); // "Hello, I'm John"
},
// ES6 method shorthand: this -> person
introduce() {
console.log(`I'm ${this.name}, ${this.age} years old`);
},
// arrow function: this inherited from outer scope
arrowGreet: () => {
console.log(`Hello, I'm ${this.name}`); // usually undefined for name
},
};
person.greet();
person.introduce();
person.arrowGreet();
5. this in constructor functionsβ
function Person(name, age) {
this.name = name;
this.age = age;
this.greet = function () {
console.log(`Hello, I'm ${this.name}`);
};
}
const john = new Person('John', 30);
john.greet();
console.log(john.name); // "John"
6. this in classesβ
class Person {
constructor(name) {
this.name = name;
}
// regular method: this -> instance
greet() {
console.log(`Hello, I'm ${this.name}`);
}
// arrow function class field: this permanently bound to instance
arrowGreet = () => {
console.log(`Hi, I'm ${this.name}`);
};
}
const john = new Person('John');
john.greet(); // "Hello, I'm John"
// method extraction loses this
const greet = john.greet;
greet(); // error in strict mode
// arrow field keeps this
const arrowGreet = john.arrowGreet;
arrowGreet(); // "Hi, I'm John"
3. Quiz: What will be printed?β
Quiz: what will the following code print?
Question 1: object method vs arrow functionβ
const obj = {
name: 'Object',
regularFunc: function () {
console.log('A:', this.name);
},
arrowFunc: () => {
console.log('B:', this.name);
},
};
obj.regularFunc();
obj.arrowFunc();
Click to view answer
// A: Object
// B: undefined
Explanation:
regularFuncis called asobj.regularFunc(), sothisisobjarrowFunchas no ownthis; it inherits outer/global lexicalthis
Question 2: passing function as argumentβ
const person = {
name: 'John',
greet: function () {
console.log(`Hello, ${this.name}`);
},
};
person.greet(); // 1
const greet = person.greet;
greet(); // 2
setTimeout(person.greet, 1000); // 3
Click to view answer
// 1: "Hello, John"
// 2: "Hello, undefined" or error in strict mode
// 3: "Hello, undefined" or error in strict mode
Explanation:
person.greet()-> implicit binding,thisisperson- Extracted function call ->
thisis lost - Callback passed to
setTimeout->thisis notperson
Question 3: nested functionsβ
const obj = {
name: 'Outer',
method: function () {
console.log('A:', this.name);
function inner() {
console.log('B:', this.name);
}
inner();
const arrow = () => {
console.log('C:', this.name);
};
arrow();
},
};
obj.method();
Click to view answer
// A: Outer
// B: undefined
// C: Outer
Explanation:
A:methodis called byobjB:inneris a regular direct callC: arrow function inherits outermethodthis
Question 4: setTimeout and arrow functionβ
const obj = {
name: 'Object',
method1: function () {
setTimeout(function () {
console.log('A:', this.name);
}, 100);
},
method2: function () {
setTimeout(() => {
console.log('B:', this.name);
}, 100);
},
};
obj.method1();
obj.method2();
Click to view answer
// A: undefined
// B: Object
Explanation:
A: regular callback insetTimeoutloses method contextB: arrow callback inheritsthisfrommethod2
Question 5: complex this bindingβ
const obj1 = {
name: 'obj1',
getThis: function () {
return this;
},
};
const obj2 = {
name: 'obj2',
};
console.log('A:', obj1.getThis().name);
const getThis = obj1.getThis;
console.log('B:', getThis() === window); // browser assumption
obj2.getThis = obj1.getThis;
console.log('C:', obj2.getThis().name);
const boundGetThis = obj1.getThis.bind(obj2);
console.log('D:', boundGetThis().name);
Click to view answer
// A: obj1
// B: true
// C: obj2
// D: obj2
Explanation:
A: called fromobj1B: direct call uses default binding (window in browser non-strict)C: called fromobj2D: explicitly bound withbind(obj2)
Question 6: constructor and prototypeβ
function Person(name) {
this.name = name;
}
Person.prototype.greet = function () {
console.log(`Hello, I'm ${this.name}`);
};
Person.prototype.delayedGreet = function () {
setTimeout(function () {
console.log('A:', this.name);
}, 100);
};
Person.prototype.arrowDelayedGreet = function () {
setTimeout(() => {
console.log('B:', this.name);
}, 100);
};
const john = new Person('John');
john.delayedGreet();
john.arrowDelayedGreet();
Click to view answer
// A: undefined
// B: John
Explanation:
A: regular timeout callback uses default/global bindingB: arrow timeout callback inherits instancethis
Question 7: global variable vs object propertyβ
var name = 'jjjj';
var obj = {
a: function () {
name = 'john';
console.log(this.name);
},
};
obj.a();
Click to view answer
// undefined
Explanation:
The key is the difference between global variables and object properties.
thisinobj.a()points toobjname = 'john'(without declaration) updates the global variablethis.namereadsobj.nameobjhas nonameproperty, so it isundefined
Execution flow:
// initial
window.name = 'jjjj';
obj = {
a: function () {
/* ... */
},
// obj has no name property
};
obj.a();
β
window.name = 'john'; // global value changed
this.name; // equals obj.name
obj.name; // undefined
If you want 'john', assign via this.name = 'john'.
var obj = {
a: function () {
this.name = 'john';
console.log(this.name); // 'john'
},
};
obj.a();
console.log(obj.name); // 'john'
Question 8: global variable trap (extended)β
var name = 'global';
var obj = {
name: 'object',
a: function () {
name = 'modified';
console.log('1:', name); // global variable
console.log('2:', this.name); // object property
},
};
obj.a();
console.log('3:', name); // global variable
console.log('4:', obj.name); // object property
Click to view answer
// 1: modified
// 2: object
// 3: modified
// 4: object
Key point:
namewithoutthis.-> global variablethis.name-> object property- They are different values
4. How to preserve this in callbacks?β
How to preserve
thisinside callback functions?
Method 1: arrow functionβ
const obj = {
name: 'Object',
method: function () {
setTimeout(() => {
console.log(this.name); // "Object"
}, 1000);
},
};
obj.method();
Method 2: bind()β
const obj = {
name: 'Object',
method: function () {
setTimeout(
function () {
console.log(this.name); // "Object"
}.bind(this),
1000
);
},
};
obj.method();
Method 3: store this in variable (legacy pattern)β
const obj = {
name: 'Object',
method: function () {
const self = this;
setTimeout(function () {
console.log(self.name); // "Object"
}, 1000);
},
};
obj.method();
Method 4: call() / apply()β
function greet() {
console.log(`Hello, I'm ${this.name}`);
}
const person1 = { name: 'John' };
const person2 = { name: 'Jane' };
greet.call(person1); // "Hello, I'm John"
greet.apply(person2); // "Hello, I'm Jane"
5. Common this pitfallsβ
Common
thispitfalls
Pitfall 1: extracting object methodβ
const obj = {
name: 'Object',
greet: function () {
console.log(this.name);
},
};
obj.greet(); // β
"Object"
const greet = obj.greet;
greet(); // β this lost
const boundGreet = obj.greet.bind(obj);
boundGreet(); // β
"Object"
Pitfall 2: this in event listenersβ
const button = document.querySelector('button');
const obj = {
name: 'Object',
// β arrow function here does not bind to button
handleClick1: () => {
console.log(this); // window/global lexical
},
// β
regular function in listener gets event target as this
handleClick2: function () {
console.log(this); // button element
},
// β
use arrow wrapper when you need object this inside callback
handleClick3: function () {
button.addEventListener('click', () => {
console.log(this.name); // "Object"
});
},
};
Pitfall 3: callback in array methodsβ
const obj = {
name: 'Object',
items: [1, 2, 3],
// β regular callback loses this
processItems1: function () {
this.items.forEach(function (item) {
console.log(this.name, item);
});
},
// β
arrow callback keeps lexical this
processItems2: function () {
this.items.forEach((item) => {
console.log(this.name, item);
});
},
// β
use thisArg
processItems3: function () {
this.items.forEach(function (item) {
console.log(this.name, item);
}, this);
},
};
6. this binding rules summaryβ
Summary of
thisbinding rules
Priority (high -> low)β
// 1. new binding (highest)
function Person(name) {
this.name = name;
}
const john = new Person('John');
console.log(john.name); // "John"
// 2. explicit binding (call/apply/bind)
function greet() {
console.log(this.name);
}
const obj = { name: 'Object' };
greet.call(obj); // "Object"
// 3. implicit binding (object method)
const obj2 = {
name: 'Object2',
greet: greet,
};
obj2.greet(); // "Object2"
// 4. default binding (lowest)
greet(); // undefined (strict) or global name (non-strict)
Function vs Arrow Functionβ
| Feature | Function | Arrow Function |
|---|---|---|
Has its own this | β Yes | β No |
this determined by | Call site | Lexical definition scope |
call/apply/bind can change this | β Yes | β No |
| Can be constructor | β Yes | β No |
Has arguments | β Yes | β No |
| Best for | Object methods, constructors | Callbacks, inherited outer this |
Memory phraseβ
βArrow inherits, function depends on call.β
- Arrow function:
thisis inherited from outer lexical scope- Regular function:
thisis decided at runtime by call site
7. Best practicesβ
Best practices
β Recommendedβ
// 1. Use regular function or method shorthand for object methods
const obj = {
name: 'Object',
// β
good
greet: function () {
console.log(this.name);
},
// β
good
introduce() {
console.log(this.name);
},
};
// 2. Use arrow functions for callbacks that should keep outer this
class Component {
constructor() {
this.name = 'Component';
}
mount() {
setTimeout(() => {
console.log(this.name);
}, 1000);
}
}
// 3. Use regular function when dynamic this is needed
const button = {
label: 'Click me',
handleClick: function () {
console.log(this); // event target / receiver object
},
};
β Not recommendedβ
// 1. Avoid arrow function as object methods
const obj = {
name: 'Object',
greet: () => {
console.log(this.name); // undefined in most cases
},
};
// 2. Avoid arrow function as constructor
const Person = (name) => {
this.name = name; // wrong
};
// 3. Avoid arrow when you need arguments object
const sum = () => {
console.log(arguments); // ReferenceError
};