JavaScript
Dynamic Context
const obj = {
firstName: 'Arfat',
print: function() {
console.log(this.firstName);
}
};
setTimeout(obj.print, 100);
var obj = {
firstName: 'Jane',
friends: ['Tarzan', 'Cheeta'],
loop: function () {
this.friends.forEach(function (friend) {
console.log(this.firstName + ' knows ' + friend);
});
},
};
obj.loop();
1
2
Did you get...
1
undefined
2
undefined knows Tarzan undefined knows Cheeta
let obj = {
score: 200,
getScore() {
console.log(this.score);
},
};
doSomething(obj.getScore);
Assume doSomething is a function which accepts a function as an argument and calls it.
Not calling getScore. Just passing a reference.
Object have Methods
let obj = {
firstName: 'arfat',
print() {
return this.firstName;
},
};
Methods are actions that can be performed on objects.
What is the value of the `this` keyword in a method?
In Java (statically bound)
class Person {
String name = "arfat";
void printName() {
System.out.println(this.name);
System.out.println(name);
}
}
public class MyClass {
public static void main(String args[]) {
Person p = new Person();
System.out.println("in main()");
p.printName();
}
}
Every function is a method.
That is, there are no functions. Only methods.
In C++ (static bind)
class Person {
string name = "arfat";
public:
void printName() {
cout<<this->name;
}
};
int main() {
Person p;
cout<<"in main"<<endl;
p.printName();
return 0;
}
In C++ (static bind)
class Person {
string name = "arfat";
public:
void printName() {
cout<<this->name;
}
};
void printName2() {
cout<<this->name;
}
int main() {
Person p;
cout<<"in main"<<endl;
p.printName();
return 0;
}
error: invalid use of ‘this’ in non-member function
Clear distinction between functions and methods
In JS
Every function is a method
Every method is a function
In other words, there is no distinction between functions and methods.
Then, what is the value of `this` in a method?
There is no value of the `this` keyword to begin with.
The object to which `this` is will be bound to is determined at runtime depending on the how the function was executed / called.
This can be stated in short, by saying, the `this` keyword is dynamically bound.
A function can become a method of any object
global.text = ' value on the global object';
function print() {
console.log('I am printing -- ', this.text);
}
let obj1 = {
text: 'values of OBJ1',
print: print,
};
let obj2 = {
text: 'values of OBJ2',
print,
};
print();
obj1.print();
obj2.print();
- this is not a compile-time binding but is a run-time binding.
- this has nothing to do with where and how the function is declared but it has everything to do with how that function is invoked/called.
Important Rules
`this` inside methods
- The `this` value can be modified in a couple of ways.
- Default binding (when no object is supplied)
- Implicit binding (when an object is supplied naturally)
- Arrow functions (when arrow functions are used)
- Explicit binding (When call, bind, or apply is used)
- new binding (When `new` keyword is used)
- strict mode (When using 'use strict')
- Contextual (libraries or framework may change values, for e.g. Event emitters)
- Modules
- Others
Default binding
- Standalone function invocation.
- If no other binding rule matches, then it is default binding. It sort of works like default catch-all rule.
function add1(num1, num2) {
return num1 + num2;
}
const add2 = function(num1, num2) {
return num1 + num2;
}
add1(10,15)
add2(10,12)
function print() {
console.log(this.firstName)
}
print()
Default binding is the global object*
When a function is executed normally (without any help of object), the this context is set to global.
function f() {
return this;
}
// in nodejs
console.log(f() === global);
// in browsers
console.log(f() === window);
*in ES5
Implicit binding
function foo() {
console.log(this.a);
}
const obj = {
a: 2,
foo: foo,
};
obj.foo();
Implicit binding rule says that it's adjoining object which should be used for the function call's this binding.
Problem: Implicit bind can be lost
function foo() {
console.log(this.a);
}
const obj = {
a: 2,
foo: foo,
};
const bar = obj.foo;
bar();
Losing `this` When Extracting a Method
let counter = {
count: 0,
inc: function () {
this.count++;
}
}
-
We have called the value of counter.inc as a function.
-
Hence, this is the global object and we have performed window.count++ .
-
window.count does not exist and is undefined . Applying the ++ operator to it sets it to NaN.
-
Use strict mode for avoiding this.
> let func = counter.inc;
> func()
> counter.count // didn’t work
0
Explicit binding
The solution to the previous problem is explicit binding.
- bind
- call
- apply
The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.
var module = {
x: 42,
getX: function() {
return this.x;
}
}
var unboundGetX = module.getX;
console.log(unboundGetX());
// The function gets invoked at the global scope
// expected output: undefined
var boundGetX = unboundGetX.bind(module);
console.log(boundGetX());
// expected output: 42
Syntax: function.bind(thisArg[, arg1[, arg2[, ...]]])
.bind
How to properly extract a method
> var func3 = counter.inc.bind(counter);
> func3()
> counter.count // it worked!
1
Callbacks and extracted methods
function callIt(callback) {
callback();
}
> callIt(counter.inc)
❌
✓
> callIt(counter.inc.bind(counter))
The call() method calls a function with a given this value and arguments provided individually.
Syntax: function.call(thisArg, arg1, arg2, ...)
function greet() {
var reply = [
this.animal,
'typically sleep between',
this.sleepDuration
].join(' ');
console.log(reply);
}
var obj = {
animal: 'cats', sleepDuration: '12 and 16 hours'
};
greet.call(obj);
// cats typically sleep between 12 and 16 hours
.call
The apply() method calls a function with a given this value, and arguments provided as an array (or an array-like object).
var numbers = [5, 6, 2, 3, 7];
var max = Math.max.apply(null, numbers);
console.log(max);
// expected output: 7
var min = Math.min.apply(null, numbers);
console.log(min);
// expected output: 2
Syntax: function.apply(thisArg, [argsArray])
.apply
Note: While the syntax of apply() is almost identical to that of call(), the fundamental difference is that call() accepts an argument list, while apply() accepts a single array of arguments.
With call, you have to know the arguments at compile time. With apply, you can defer that decision at runtime.
Problem: Functions Inside Methods Shadow this
let obj = {
firstName: 'Jane',
friends: ['Tarzan', 'Cheeta'],
loop: function () {
this.friends.forEach(function (friend) {
console.log(this.firstName + ' knows ' + friend);
});
},
};
obj.loop();
Problem: Functions Inside Methods Shadow this
function callback_ForEach(friend) {
console.log(this.firstName + ' knows ' + friend);
}
let obj = {
firstName: 'Jane',
friends: ['Tarzan', 'Cheeta'],
loop: function () {
this.friends.forEach(callback_ForEach);
},
};
obj.loop();
Workaround 1: that = this
loop: function () {
'use strict';
var that = this;
this.friends.forEach(function (friend) {
console.log(that.name+' knows '+friend);
});
}
Workaround 2: bind()
loop: function () {
'use strict';
this.friends.forEach(function (friend) {
console.log(this.name+' knows '+friend);
}.bind(this)); // (1)
}
Workaround 3: a thisValue for forEach()
this.friends.forEach(function (friend) {
console.log(this.name+' knows '+friend);
}, this);
At this point, even JS developers said
So they created another function with reduced power.
Arrow Functions
() => { ... } // no parameter
x => { ... } // one parameter, an identifier
(x, y) => { ... } // several parameters
x => { return x * x } // block
x => x * x
const arr = [1, 2, 3];
const squares = arr.map(x => x * x);
New Kind of Function
Specifying a body:
The complete list of variables whose values are determined lexically is:
arguments super this new.target
var obj = {
name: 'Jane',
friends: [ 'Tarzan', 'Cheeta' ],
loop: function () {
this.friends.forEach(
(friend) => {
console.log(this.name+' knows '+friend);
}
);
}
};
obj.loop()
Workaround 4: Arrow functions
- The following constructs are lexical: arguments, super, this, new.target
- It can’t be used as a constructor: Normal functions support new via the internal method [[Construct]] and the property prototype. Arrow functions have neither, which is why new (() => {}) throws an error.
Arrow functions versus normal functions
An arrow function is different from a normal function in only two ways:
const obj = {
firstName: 'Arfat',
print: function() {
console.log(this.firstName);
}
};
setTimeout(obj.print, 100);
Example
- Remember to find the the execution place
- Not the place of definition
In DOM event handler
this is set to the element the event fired from
function bluify(e) {
// Always true
console.log(this === e.currentTarget);
// true when currentTarget and target are the same object
console.log(this === e.target);
this.style.backgroundColor = '#A5D9F3';
}
target = element that triggered event; currentTarget = element that listens to event.
In an inline event handler
<button onclick="alert(this.tagName.toLowerCase());">
Show this
</button>
When the code is called from an inline on-event handler, its this is set to the DOM element on which the listener is placed:
In ES6 modules
`this` is bound to undefined
In Node CJS module system
this is bound to the exports obejct
<script src="script.js" type="module"></script>
Strict Mode vs Sloppy Mode
- JavaScript's strict mode, introduced in ECMAScript 5, is a way to opt in to a restricted variant of JavaScript, thereby implicitly opting-out of "sloppy mode".
- Strict mode isn't just a subset: it intentionally has different semantics from normal code.
To invoke strict mode for an entire script, put the exact statement "use strict";
Strict mode applies to entire scripts or to individual functions.
- Eliminates some JavaScript silent errors by changing them to throw errors.
- Fixes mistakes that make it difficult for JavaScript engines to perform optimizations
- Prohibits some syntax likely to be defined in future versions of ECMAScript.
Strict mode makes several changes to normal JavaScript semantics:
'use strict';
foo = 17;
foo = 17;
var undefined = 9
var Infinity = 10;
if (undefined) {
console.log('is true');
}
'use strict';
var undefined = 9;
var Infinity = 10;
if (undefined) {
console.log('is true');
}
Context in Strict mode
In sloppy mode, this is always an object:
- either the provided object if called with an object-valued this; the value, boxed, if called with a Boolean, string, or number this;
- or the global object if called with an undefined or null this.
function fun() { return this; }
fun() === global;
typeof fun.call(2) === 'object';
fun.apply(null) === global;
fun.call(undefined) === global;
typeof fun.bind(true)() === 'object';
'use strict';
function fun() { return this; }
fun() === undefined;
fun.call(2) === 2;
fun.apply(null) === null;
fun.call(undefined) === undefined;
fun.bind(true)() === true;
"new" binding
- Constructor functions “construct” objects. They are equivalent to classes in other languages.
- Since JavaScript has no concept of a class (even though it has class keyword), functions rise up to the task.
- A function, when used with the new operator, is called a constructor function. The new operator changes the behavior of the function.
function Person(name) {
this.name = name;
}
const person1 = new Person('Arfat');
console.log(person1);
// Person { name: 'Arfat' }
Dynamic Context
By Arfat Salman
Dynamic Context
- 480