Tools: Understanding the keyword ‘this’ in JavaScript

Tools: Understanding the keyword ‘this’ in JavaScript

Source: Dev.to

Introduction ## What is this? ## The problem ## The 4 rules ## Rule 1 ## Rule 2 ## Object literal ## Class constructor ## Function constructor ## Rule 3 ## Converting a method to a function ## Converting a function to a method ## call and apply ## Rule 4 ## Arrow function ## Callback function ## called as a function - Rule 1 ## called as a method - Rule 2 ## callback called by apply/call - Rule 3 ## callback created by arrow function or bind - Rule 4 ## Arrow functions ## Nested functions ## Value of this by platforms ## Browser ## Browser with strict mode ## Strict mode ## Normal mode ## Browser with module ## Nodejs ## Issues with testing ## Example ## jest test ## karma test ## Explaining the first example ## Simple function ## Arrow function ## Summary ## 4 Rules ## Callback ## Browser ## Browser with strict mode ## Browser with module ## Nodejs ## All cases in one code ## Cheat sheet The keyword this works differently for javascript language. It can change its value depending on how it is called. After facing many issues, I wrote this article explaining how it works with just 4 rules to simplify the understanding and avoid common issues like undefined method and calling wrong context. It explains how it works with callback functions, different platforms like nodejs and browser, and how to prevent bugs on testing with tools like jest. According to Mozilla, the keyword this works like below: In most cases, the value of this is determined by how a function is called (runtime binding). It can't be set by assignment during execution, and it may be different each time the function is called. The problem below shows the value of this is different in two cases See the two examples below: This first example returns [2,4,6], while the second one, gets [NaN, NaN, NaN]. At the end of this article you will understand why they have different results. There are 4 rules that helps to understand how the keyword this works: Rule 1: Inside a function, this refers to a global context(window for browser, global for nodejs); Rule 2: Inside a method, refers to an object; Rule 3: The value of this can be modified by call and apply functions and also when converts a method to a function and vice-versa; Rule 4: The value of this will never change if is in an arrow function or a bind function, therefore invalidating the rules 1, 2 and 3; Within a function, this refers to a global object. In a browser, the global object is windows, while in NodeJs is global. Even nested functions refers to a global context Within a method, this refers to an object. The code below this refers to object account. This applies to object literal, class constructor and function contructor: The value of this can be modified by using call, apply and converting a method to a function and vice-versa. The code below has two variables: the global variable balance. Line 1 the variable balance at object account. Line 2 On line 9 is doing aliasing which means, converting the method showBalance to function showBalance. The line 10 is calling a function(Rule 1), then the value of this will be global. On line 11, is a method(Rule 2), then it refers to object account. The opposite way, by assigning a function to a method, has also the same effect: Another way to change the value of this is by using call or apply. The first parameter of call/apply is the value that sets this. The value of this will never change if is in an arrow function or a bind function, therefore invalidating the rules 1, 2 and 3; As the method account.showBalance is a arrow function, then the value of this will always refer to account object, the showBalance will always return the same value regardless how it has been called. The same behavior for bind. The accountShowBalance is a bind function, then the this refers to account object. The showBalance is just a normal function, then the this value refers to global context, that's why it displayed undefined. Depending how a callback function is called and created the this will have different values. The result will be 200. Why? As the callback has been created as function at line 9 and it was called as function at line 5, the this will refer to window that contains the price 200. At the line 5, the callback function is assigned into a method, it is available now by product.callback. At the line 6, as it is been called by this which refers product object, so the line 11 returns 100. The context of this of callback can be modified by apply or call. At the line 7, the contenxt of this is changed to product2, then line 12 will return 300. If the callback is created by arrow function or bind, the value of this never changes. The arrow functions will access the enclosing lexical context's this, in another words, it will access the this from closest constructor function or class that surrounds it. The line 4 declares an arrow function. As it is in the constructor function Person, then this value will be the instance of Person; If a nested arrow function is created, the this' value will still be the same. The arrow function set this value from the closest constructor function or class. The line 4, the variable getParentName is inside of Person function. When it creates an arrow function, the lexical context is Person, so the this value belong to Person instance. The line 10 and 14 create an arrow function, but the lexical context now is Child which is a constructor function as it will be instantiated on line 19. Arrow function won't access object literal's context like a method. On object literal, contructor or classes, the value of this refers to the context of the object. On line 4, the function showName is assigned inside of person object, then its this will access the object. On line 6, the child is another object literal, then the line 9 this will accesss child context. After instantiated with keyword new on lines 29 and 30, the lines 17 and 26 will access the context of the object instantiate from PersonContructor and PersonClass. The value of this is different by plataform (browser or nodejs), strict mode or if is a ES6 module. In browser the value of this in function(Rule 1) refers to window. Look the code below: The lines 1 and 2 create global variables a and b. The individual function hello can access the global object by this. The lines 5 and 6 prints the hello and world. When the ’use strict’ is added to top of the file or function, the JavaScript stops to defaulting this to window object. ES6 Introduced the "module". It doesn't even have global/window context. So, the code below won't work either. Create a module with the code below: Create the file hello.js with content below: Create a file index.html then add within the tag <head> the line below: Open the file in browser. The code will fail on line 4, this can't access window. I would recommend to use "modules", because it can be transpiled with webpack or similar, and work in both environments browser and nodejs. If you want to learn to write a testable javascript code with jquery to run in both environment, read my article. In nodejs works a little bit different. The this is a global value. The variable a can't be access from global.a. The this access only global object. Sometimes a JavaScript code can work on browser but its unit test fails. This happens quite often in legacy code that contains global variables spread among several files and consumed by this in somewhere. If this legacy code has been tested in tools like jest and karma they might have different results. Consider a legacy app where two JavaScript files are loaded in browser by "script" tags. The example is available on GitHub. globalState.js: Initialize global variables, legacy.js: Access global variables through this. Below are test files to verify if the functions "getGlobalValue" and getGlobalValue2" returns right values. The first one is written in jest while the second in karma. Both tests in karma will all pass, while the jest will fail on the first test result is 10. Why? The issue is in the line 1 of globalState.js. As jest runs in a Nodejs environment and Karma in browser environment, the variable globalValue = 10 will be available only on browser’s object window but not in NodeJS’s object global. This is deeply explained on "Value of this by plataforms" item. If the line 1 is replace by window like below, the code will work on both jest and karma globalState.js modified jest will automatically copy all window values into nodejs global object. Then the jest test will pass. To help to understand how the native JavaScript function .map works under the hood, let’s implement a custom version called modifyArray. The function modifyArray receives an array. It will iterate each element and replace it with the return of the callback function. Note that the callback function is using the .call function changing the value of this by the value thisArg as it shows on the 4th line. Let's multiply all the elements and passing a simple function multiply as a callback to the function modifyArray: The result will be [2,4,6]. On the 5th line the third parameter thisArg has the value 2. In the modifyArray callback.call(thisArg, array[i]) update the this value with 2. According to the rule 3, because the multiply function was called using call, then the value of this will be 2. Let's modify the ’multiply function to an arrow function: The result will be [NaN, NaN, NaN]. Because the callback is an arrow function a two things happend here: The line callback.call(thisArg, array[i]) from modifyArray doesn’t change the value of this, look the Rule 4. The value of this is window because it set the this to the nearest scope, which in this case is window When it calls element*this it tries to multiply a number with the window value element * window resulting to NaN value. Now, by understanding the previous example, the same idea may apply to the function map. The map function has two parameters function and thisArg. The thisArg will be the value of this inside a function callback. In arrow function the value of this won't be thisArg. Inside the map function it does something similar to our function modifyArray. It might use the call or apply method to change the value of this. Inside a function, this refers to a global context(window for browser, global for nodejs); Inside a method, refers to an object; The value of this can be modified by call and apply functions and also when converts a method to a function and vice-versa; The value of this will never change if is in an arrow function or a bind function, therefore invalidating the rules 1, 2 and 3; If the callback is a bind function or an arrow function, the value of this will never change regardless of how it is called. Rule 4 if the callback is just a function, the value of this depends on how will be called then could apply the rules 1, 2 or 3. declare var outside any function is accessible via this declare var outside any function is NOT accessible via this Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK: const a = [1,2,3].map(function(n){ return n*this; }, 2); Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: const a = [1,2,3].map(function(n){ return n*this; }, 2); CODE_BLOCK: const a = [1,2,3].map(function(n){ return n*this; }, 2); CODE_BLOCK: [2,4,6] Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: const b = [1,2,3].map( n => { return n*this; }, 2); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: const b = [1,2,3].map( n => { return n*this; }, 2); COMMAND_BLOCK: const b = [1,2,3].map( n => { return n*this; }, 2); CODE_BLOCK: [NaN,NaN,NaN] Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: [NaN,NaN,NaN] CODE_BLOCK: [NaN,NaN,NaN] CODE_BLOCK: function print(){ console.log(this == window); } print() //true Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: function print(){ console.log(this == window); } print() //true CODE_BLOCK: function print(){ console.log(this == window); } print() //true CODE_BLOCK: function print(){ console.log(this == window); function print2(){ console.log(this == window); } print2(); //true } print() //true Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: function print(){ console.log(this == window); function print2(){ console.log(this == window); } print2(); //true } print() //true CODE_BLOCK: function print(){ console.log(this == window); function print2(){ console.log(this == window); } print2(); //true } print() //true CODE_BLOCK: const account = { balance: 100, showBalance: function(value){ console.log(this.balance) } } account.showBalance(); //100 Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: const account = { balance: 100, showBalance: function(value){ console.log(this.balance) } } account.showBalance(); //100 CODE_BLOCK: const account = { balance: 100, showBalance: function(value){ console.log(this.balance) } } account.showBalance(); //100 CODE_BLOCK: class Account{ constructor(){ this.balance = 100; } showBalance(value){ console.log(this.balance) } } const account = new Account(); account.showBalance(); //100 Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: class Account{ constructor(){ this.balance = 100; } showBalance(value){ console.log(this.balance) } } const account = new Account(); account.showBalance(); //100 CODE_BLOCK: class Account{ constructor(){ this.balance = 100; } showBalance(value){ console.log(this.balance) } } const account = new Account(); account.showBalance(); //100 CODE_BLOCK: function Account(){ this.balance = 100; this.showBalance = function(value){ console.log(this.balance) } } const account = new Account(); account.showBalance(); //100 Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: function Account(){ this.balance = 100; this.showBalance = function(value){ console.log(this.balance) } } const account = new Account(); account.showBalance(); //100 CODE_BLOCK: function Account(){ this.balance = 100; this.showBalance = function(value){ console.log(this.balance) } } const account = new Account(); account.showBalance(); //100 CODE_BLOCK: var balance = 500; const account = { balance: 100, showBalance: function(value){ console.log(this.balance); } } const showBalance = account.showBalance; //aliasing showBalance(); //500 account.showBalance(); //100 Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: var balance = 500; const account = { balance: 100, showBalance: function(value){ console.log(this.balance); } } const showBalance = account.showBalance; //aliasing showBalance(); //500 account.showBalance(); //100 CODE_BLOCK: var balance = 500; const account = { balance: 100, showBalance: function(value){ console.log(this.balance); } } const showBalance = account.showBalance; //aliasing showBalance(); //500 account.showBalance(); //100 CODE_BLOCK: var balance = 500; function showBalance(value){ console.log(this.balance); } const account = { balance: 100 } account.showBalance = showBalance; showBalance(); //500 account.showBalance(); //100 Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: var balance = 500; function showBalance(value){ console.log(this.balance); } const account = { balance: 100 } account.showBalance = showBalance; showBalance(); //500 account.showBalance(); //100 CODE_BLOCK: var balance = 500; function showBalance(value){ console.log(this.balance); } const account = { balance: 100 } account.showBalance = showBalance; showBalance(); //500 account.showBalance(); //100 CODE_BLOCK: function showBalance(value){ console.log(this.balance); } const account1 = { balance: 100 } const account2 = { balance: 50 } showBalance.call(account1); //100 showBalance.apply(account2); //50 Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: function showBalance(value){ console.log(this.balance); } const account1 = { balance: 100 } const account2 = { balance: 50 } showBalance.call(account1); //100 showBalance.apply(account2); //50 CODE_BLOCK: function showBalance(value){ console.log(this.balance); } const account1 = { balance: 100 } const account2 = { balance: 50 } showBalance.call(account1); //100 showBalance.apply(account2); //50 COMMAND_BLOCK: function Account() { this.balance = 100; this.showBalance = () => { console.log(this.balance); }; } const account = new Account(); const showBalance = account.showBalance; account.showBalance(); //100 showBalance(); //100 showBalance.call(); //100 showBalance.apply(); //100 Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: function Account() { this.balance = 100; this.showBalance = () => { console.log(this.balance); }; } const account = new Account(); const showBalance = account.showBalance; account.showBalance(); //100 showBalance(); //100 showBalance.call(); //100 showBalance.apply(); //100 COMMAND_BLOCK: function Account() { this.balance = 100; this.showBalance = () => { console.log(this.balance); }; } const account = new Account(); const showBalance = account.showBalance; account.showBalance(); //100 showBalance(); //100 showBalance.call(); //100 showBalance.apply(); //100 CODE_BLOCK: function Account() { this.balance = 100; } const account = new Account(); function showBalance(){ console.log(this.balance); } accountShowBalance = showBalance.bind(account); accountShowBalance(); //100 accountShowBalance.call(); //100 accountShowBalance.apply(); //100 showBalance(); //undefined Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: function Account() { this.balance = 100; } const account = new Account(); function showBalance(){ console.log(this.balance); } accountShowBalance = showBalance.bind(account); accountShowBalance(); //100 accountShowBalance.call(); //100 accountShowBalance.apply(); //100 showBalance(); //undefined CODE_BLOCK: function Account() { this.balance = 100; } const account = new Account(); function showBalance(){ console.log(this.balance); } accountShowBalance = showBalance.bind(account); accountShowBalance(); //100 accountShowBalance.call(); //100 accountShowBalance.apply(); //100 showBalance(); //undefined CODE_BLOCK: var price = 200; const product = { price: 100, showPrice: function(callback){ callback(); // Rule 1 } } product.showPrice(function(){ console.log(this.price); //200 }); Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: var price = 200; const product = { price: 100, showPrice: function(callback){ callback(); // Rule 1 } } product.showPrice(function(){ console.log(this.price); //200 }); CODE_BLOCK: var price = 200; const product = { price: 100, showPrice: function(callback){ callback(); // Rule 1 } } product.showPrice(function(){ console.log(this.price); //200 }); CODE_BLOCK: var price = 200; const product = { price: 100, showPrice: function(callback){ this.callback = callback; // converting to method - Rule 3 this.callback(); // method - Rule 2 } } product.showPrice(function(){ console.log(this.price); }); Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: var price = 200; const product = { price: 100, showPrice: function(callback){ this.callback = callback; // converting to method - Rule 3 this.callback(); // method - Rule 2 } } product.showPrice(function(){ console.log(this.price); }); CODE_BLOCK: var price = 200; const product = { price: 100, showPrice: function(callback){ this.callback = callback; // converting to method - Rule 3 this.callback(); // method - Rule 2 } } product.showPrice(function(){ console.log(this.price); }); CODE_BLOCK: const product2 = { price: 300 } const product = { price: 100, showPrice: function(callback){ callback.apply(product2); //Rule 3 } } product.showPrice(function(){ console.log(this.price); //300 }); Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: const product2 = { price: 300 } const product = { price: 100, showPrice: function(callback){ callback.apply(product2); //Rule 3 } } product.showPrice(function(){ console.log(this.price); //300 }); CODE_BLOCK: const product2 = { price: 300 } const product = { price: 100, showPrice: function(callback){ callback.apply(product2); //Rule 3 } } product.showPrice(function(){ console.log(this.price); //300 }); CODE_BLOCK: var value = 'value from global'; function App(){ this.value = 'value from app'; this.addCallback = function(callback){ this.callback = callback; } this.triggerCallback = function(){ const callback = this.callback; this.callback(); callback(); } } function System(){ this.value = 'value from system'; function callback(){ console.log(this.value); } this.run = function(){ var app = new App(); app.addCallback(()=>{ //Rule 4 console.log(this.value); }); app.triggerCallback(); //value from system //value from system app.addCallback(callback.bind(this)); //Rule 4 app.triggerCallback(); //value from system //value from system app.addCallback(callback); //Rule 3 app.triggerCallback(); //value from app //value from global } } var system = new System(); system.run(); Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: var value = 'value from global'; function App(){ this.value = 'value from app'; this.addCallback = function(callback){ this.callback = callback; } this.triggerCallback = function(){ const callback = this.callback; this.callback(); callback(); } } function System(){ this.value = 'value from system'; function callback(){ console.log(this.value); } this.run = function(){ var app = new App(); app.addCallback(()=>{ //Rule 4 console.log(this.value); }); app.triggerCallback(); //value from system //value from system app.addCallback(callback.bind(this)); //Rule 4 app.triggerCallback(); //value from system //value from system app.addCallback(callback); //Rule 3 app.triggerCallback(); //value from app //value from global } } var system = new System(); system.run(); CODE_BLOCK: var value = 'value from global'; function App(){ this.value = 'value from app'; this.addCallback = function(callback){ this.callback = callback; } this.triggerCallback = function(){ const callback = this.callback; this.callback(); callback(); } } function System(){ this.value = 'value from system'; function callback(){ console.log(this.value); } this.run = function(){ var app = new App(); app.addCallback(()=>{ //Rule 4 console.log(this.value); }); app.triggerCallback(); //value from system //value from system app.addCallback(callback.bind(this)); //Rule 4 app.triggerCallback(); //value from system //value from system app.addCallback(callback); //Rule 3 app.triggerCallback(); //value from app //value from global } } var system = new System(); system.run(); COMMAND_BLOCK: function Person(){ //contructor this.name = 'Foo'; const getName = () => { return this.name; } this.show = function(){ console.log(getName()); } } const p = new Person(); p.show(); class PersonClass{ //contructor constructor(){ this.name = 'Foo'; } show(){ const getName = () => { return this.name; } console.log(getName()); } } const pc = new PersonClass(); pc.show(); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: function Person(){ //contructor this.name = 'Foo'; const getName = () => { return this.name; } this.show = function(){ console.log(getName()); } } const p = new Person(); p.show(); class PersonClass{ //contructor constructor(){ this.name = 'Foo'; } show(){ const getName = () => { return this.name; } console.log(getName()); } } const pc = new PersonClass(); pc.show(); COMMAND_BLOCK: function Person(){ //contructor this.name = 'Foo'; const getName = () => { return this.name; } this.show = function(){ console.log(getName()); } } const p = new Person(); p.show(); class PersonClass{ //contructor constructor(){ this.name = 'Foo'; } show(){ const getName = () => { return this.name; } console.log(getName()); } } const pc = new PersonClass(); pc.show(); CODE_BLOCK: foo foo Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: function Person(){ this.name = 'Foo'; const getName = () => { const nestedGetName = () => { return this.name; } return nestedGetName(); } this.show = function(){ console.log(getName()); } } const p = new Person(); p.show(); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: function Person(){ this.name = 'Foo'; const getName = () => { const nestedGetName = () => { return this.name; } return nestedGetName(); } this.show = function(){ console.log(getName()); } } const p = new Person(); p.show(); COMMAND_BLOCK: function Person(){ this.name = 'Foo'; const getName = () => { const nestedGetName = () => { return this.name; } return nestedGetName(); } this.show = function(){ console.log(getName()); } } const p = new Person(); p.show(); CODE_BLOCK: foo Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: function Person(){ this.name = 'Foo'; const getParentName = () => { return this.name; } function Child(){ this.name = 'bar'; this.surname = ' surname'; const getSurname = ()=>{ return this.surname; } this.getName = () =>{ return this.name+getSurname(); } } var child = new Child(); this.showChild = function(){ console.log(child.getName()); } this.show = function(){ console.log(getParentName()); } } const p = new Person(); p.show(); p.showChild(); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: function Person(){ this.name = 'Foo'; const getParentName = () => { return this.name; } function Child(){ this.name = 'bar'; this.surname = ' surname'; const getSurname = ()=>{ return this.surname; } this.getName = () =>{ return this.name+getSurname(); } } var child = new Child(); this.showChild = function(){ console.log(child.getName()); } this.show = function(){ console.log(getParentName()); } } const p = new Person(); p.show(); p.showChild(); COMMAND_BLOCK: function Person(){ this.name = 'Foo'; const getParentName = () => { return this.name; } function Child(){ this.name = 'bar'; this.surname = ' surname'; const getSurname = ()=>{ return this.surname; } this.getName = () =>{ return this.name+getSurname(); } } var child = new Child(); this.showChild = function(){ console.log(child.getName()); } this.show = function(){ console.log(getParentName()); } } const p = new Person(); p.show(); p.showChild(); CODE_BLOCK: Foo bar surname Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: Foo bar surname CODE_BLOCK: Foo bar surname CODE_BLOCK: function Person(){ this.name = 'Parent'; this.child = { name: 'child 1', show: () =>{ console.log(this.name); } } this.child2 = { name: 'child 2', show: function(){ console.log(this.name); } } } var p = new Person(); p.child.show(); p.child2.show(); Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: function Person(){ this.name = 'Parent'; this.child = { name: 'child 1', show: () =>{ console.log(this.name); } } this.child2 = { name: 'child 2', show: function(){ console.log(this.name); } } } var p = new Person(); p.child.show(); p.child2.show(); CODE_BLOCK: function Person(){ this.name = 'Parent'; this.child = { name: 'child 1', show: () =>{ console.log(this.name); } } this.child2 = { name: 'child 2', show: function(){ console.log(this.name); } } } var p = new Person(); p.child.show(); p.child2.show(); CODE_BLOCK: Parent child 2 Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: Parent child 2 CODE_BLOCK: Parent child 2 CODE_BLOCK: const person = { //object literal name: 'foo', showName: function(){ console.log(this.name); // this == person }, child: { name: 'bar', showName: function(){ console.log(this.name); // this == child } } } function PersonConstructor(){ //constructor this.name = 'foo'; this.showName = function(){ console.log(this.name); // this == PersonContructor instance } } class PersonClass{ //class constructor(){ this.name = 'foo'; // this == PersonClass instance } showName(){ console.log(this.name); // this == PersonClass instance } } const p = new PersonConstructor(); const p2 = new PersonClass(); person.showName(); person.child.showName(); p.showName(); p2.showName(); Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: const person = { //object literal name: 'foo', showName: function(){ console.log(this.name); // this == person }, child: { name: 'bar', showName: function(){ console.log(this.name); // this == child } } } function PersonConstructor(){ //constructor this.name = 'foo'; this.showName = function(){ console.log(this.name); // this == PersonContructor instance } } class PersonClass{ //class constructor(){ this.name = 'foo'; // this == PersonClass instance } showName(){ console.log(this.name); // this == PersonClass instance } } const p = new PersonConstructor(); const p2 = new PersonClass(); person.showName(); person.child.showName(); p.showName(); p2.showName(); CODE_BLOCK: const person = { //object literal name: 'foo', showName: function(){ console.log(this.name); // this == person }, child: { name: 'bar', showName: function(){ console.log(this.name); // this == child } } } function PersonConstructor(){ //constructor this.name = 'foo'; this.showName = function(){ console.log(this.name); // this == PersonContructor instance } } class PersonClass{ //class constructor(){ this.name = 'foo'; // this == PersonClass instance } showName(){ console.log(this.name); // this == PersonClass instance } } const p = new PersonConstructor(); const p2 = new PersonClass(); person.showName(); person.child.showName(); p.showName(); p2.showName(); CODE_BLOCK: foo bar foo foo Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: foo bar foo foo CODE_BLOCK: foo bar foo foo CODE_BLOCK: var a = 'world'; window.b = 'hello'; function hello(){ console.log(this.a) console.log(this.b) } hello(); Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: var a = 'world'; window.b = 'hello'; function hello(){ console.log(this.a) console.log(this.b) } hello(); CODE_BLOCK: var a = 'world'; window.b = 'hello'; function hello(){ console.log(this.a) console.log(this.b) } hello(); CODE_BLOCK: hello world Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: hello world CODE_BLOCK: hello world CODE_BLOCK: 'use strict' function showThis(){ console.log(this) // output: undefined } hello(); Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: 'use strict' function showThis(){ console.log(this) // output: undefined } hello(); CODE_BLOCK: 'use strict' function showThis(){ console.log(this) // output: undefined } hello(); CODE_BLOCK: function showThis(){ console.log(this) // output: window } hello(); Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: function showThis(){ console.log(this) // output: window } hello(); CODE_BLOCK: function showThis(){ console.log(this) // output: window } hello(); CODE_BLOCK: var a = 'world'; function hello(){ console.log(this.a)// it will fail } hello(); Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: var a = 'world'; function hello(){ console.log(this.a)// it will fail } hello(); CODE_BLOCK: var a = 'world'; function hello(){ console.log(this.a)// it will fail } hello(); CODE_BLOCK: <script type="module" src="hello.js"></script> Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: <script type="module" src="hello.js"></script> CODE_BLOCK: <script type="module" src="hello.js"></script> CODE_BLOCK: var a = 'hello'; global.b = 'world'; function hello(){ console.log(this.a) // fail console.log(this.b) // works } hello(); Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: var a = 'hello'; global.b = 'world'; function hello(){ console.log(this.a) // fail console.log(this.b) // works } hello(); CODE_BLOCK: var a = 'hello'; global.b = 'world'; function hello(){ console.log(this.a) // fail console.log(this.b) // works } hello(); CODE_BLOCK: var globalValue = 10; window.globalValue2 = 5; Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: var globalValue = 10; window.globalValue2 = 5; CODE_BLOCK: var globalValue = 10; window.globalValue2 = 5; CODE_BLOCK: function getGlobalValue() { return this.globalValue; } function getGlobalValue2() { return this.globalValue2; } window.getGlobalValue = getGlobalValue; window.getGlobalValue2 = getGlobalValue2; Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: function getGlobalValue() { return this.globalValue; } function getGlobalValue2() { return this.globalValue2; } window.getGlobalValue = getGlobalValue; window.getGlobalValue2 = getGlobalValue2; CODE_BLOCK: function getGlobalValue() { return this.globalValue; } function getGlobalValue2() { return this.globalValue2; } window.getGlobalValue = getGlobalValue; window.getGlobalValue2 = getGlobalValue2; COMMAND_BLOCK: test("result is 10", () => { expect(getGlobalValue()).toBe(10); //FAIL }); test("result is 5", () => { expect(getGlobalValue2()).toBe(5); }); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: test("result is 10", () => { expect(getGlobalValue()).toBe(10); //FAIL }); test("result is 5", () => { expect(getGlobalValue2()).toBe(5); }); COMMAND_BLOCK: test("result is 10", () => { expect(getGlobalValue()).toBe(10); //FAIL }); test("result is 5", () => { expect(getGlobalValue2()).toBe(5); }); CODE_BLOCK: describe("getGlobalValue test", function() { it("result is 10", function() { expect(getGlobalValue()).toBe(10); }); it("result is 5", function() { expect(getGlobalValue2()).toBe(5); }); }); Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: describe("getGlobalValue test", function() { it("result is 10", function() { expect(getGlobalValue()).toBe(10); }); it("result is 5", function() { expect(getGlobalValue2()).toBe(5); }); }); CODE_BLOCK: describe("getGlobalValue test", function() { it("result is 10", function() { expect(getGlobalValue()).toBe(10); }); it("result is 5", function() { expect(getGlobalValue2()).toBe(5); }); }); CODE_BLOCK: var globalValue = 10; Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: var globalValue = 10; CODE_BLOCK: var globalValue = 10; CODE_BLOCK: window.globalValue = 10; //fixed window.globalValue2 = 5; Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: window.globalValue = 10; //fixed window.globalValue2 = 5; CODE_BLOCK: window.globalValue = 10; //fixed window.globalValue2 = 5; CODE_BLOCK: function modifyArray(array, callback ,thisArg){ var newArray = []; for(var i=0;i<array.length;i++){ newArray[i] = callback.call(thisArg, array[i]); } return newArray; } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: function modifyArray(array, callback ,thisArg){ var newArray = []; for(var i=0;i<array.length;i++){ newArray[i] = callback.call(thisArg, array[i]); } return newArray; } CODE_BLOCK: function modifyArray(array, callback ,thisArg){ var newArray = []; for(var i=0;i<array.length;i++){ newArray[i] = callback.call(thisArg, array[i]); } return newArray; } CODE_BLOCK: const array = [1,2,3]; const multiply = function(element){ return element*this; } const newArray = modifyArray(array, multiply, 2); console.log(newArray); Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: const array = [1,2,3]; const multiply = function(element){ return element*this; } const newArray = modifyArray(array, multiply, 2); console.log(newArray); CODE_BLOCK: const array = [1,2,3]; const multiply = function(element){ return element*this; } const newArray = modifyArray(array, multiply, 2); console.log(newArray); CODE_BLOCK: newArray[i] = callback.call(thisArg, array[i]); //callback is `multiply` and `thisArgs` is 2. Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: newArray[i] = callback.call(thisArg, array[i]); //callback is `multiply` and `thisArgs` is 2. CODE_BLOCK: newArray[i] = callback.call(thisArg, array[i]); //callback is `multiply` and `thisArgs` is 2. COMMAND_BLOCK: const multiply = function(element){ return element*this; // => element * 2 } Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: const multiply = function(element){ return element*this; // => element * 2 } COMMAND_BLOCK: const multiply = function(element){ return element*this; // => element * 2 } COMMAND_BLOCK: const array = [1,2,3]; const multiply = () => { return element*this; // element * window } const newArray = modifyArray(array, multiply, 2); console.log(newArray); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: const array = [1,2,3]; const multiply = () => { return element*this; // element * window } const newArray = modifyArray(array, multiply, 2); console.log(newArray); COMMAND_BLOCK: const array = [1,2,3]; const multiply = () => { return element*this; // element * window } const newArray = modifyArray(array, multiply, 2); console.log(newArray); COMMAND_BLOCK: const a = [1,2,3].map(function(n){ return n*this; }, 2); const b = [1,2,3].map( n => { return n*this; }, 2); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: const a = [1,2,3].map(function(n){ return n*this; }, 2); const b = [1,2,3].map( n => { return n*this; }, 2); COMMAND_BLOCK: const a = [1,2,3].map(function(n){ return n*this; }, 2); const b = [1,2,3].map( n => { return n*this; }, 2); CODE_BLOCK: map(function callback( currentValue[, index[, array]]) { // return element for new_array }[, thisArg]) Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: map(function callback( currentValue[, index[, array]]) { // return element for new_array }[, thisArg]) CODE_BLOCK: map(function callback( currentValue[, index[, array]]) { // return element for new_array }[, thisArg]) COMMAND_BLOCK: var name = 'Foo global'; function getNameGlobalFunc(){ console.log(this.name); } const getNameGlobalVar = function(){ console.log(this.name); } const personObjectLiteral = { name: 'Foo object literal', getName: function(){ console.log(this.name); } } function PersonConstructor(){ this.name = 'Foo constructor'; this.setCallback = function( callback ){ this.callback = callback; } this.callCallback = function(){ this.callback(); } function getNameGlobal(){ console.log(this.name); } const getNameArrow = () => { console.log(this.name); }; const getNameBind = getNameGlobal.bind(this); this.getName = function(){ console.log(this.name); } this.getNameReassigned = getNameGlobal; const reasignAsGlobal = this.getName; this.callThisGlobals = function(){ getNameGlobal() reasignAsGlobal(); } this.callThisPersonContext = function(){ getNameArrow(); getNameBind(); this.getName(); this.getNameReassigned(); } } const person = new PersonConstructor(); getNameGlobalFunc(); getNameGlobalVar(); person.callThisGlobals(); personObjectLiteral.getName(); person.callThisPersonContext(); console.log(getNameGlobalVar.call(personObjectLiteral)) console.log(getNameGlobalVar.apply(person)) Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: var name = 'Foo global'; function getNameGlobalFunc(){ console.log(this.name); } const getNameGlobalVar = function(){ console.log(this.name); } const personObjectLiteral = { name: 'Foo object literal', getName: function(){ console.log(this.name); } } function PersonConstructor(){ this.name = 'Foo constructor'; this.setCallback = function( callback ){ this.callback = callback; } this.callCallback = function(){ this.callback(); } function getNameGlobal(){ console.log(this.name); } const getNameArrow = () => { console.log(this.name); }; const getNameBind = getNameGlobal.bind(this); this.getName = function(){ console.log(this.name); } this.getNameReassigned = getNameGlobal; const reasignAsGlobal = this.getName; this.callThisGlobals = function(){ getNameGlobal() reasignAsGlobal(); } this.callThisPersonContext = function(){ getNameArrow(); getNameBind(); this.getName(); this.getNameReassigned(); } } const person = new PersonConstructor(); getNameGlobalFunc(); getNameGlobalVar(); person.callThisGlobals(); personObjectLiteral.getName(); person.callThisPersonContext(); console.log(getNameGlobalVar.call(personObjectLiteral)) console.log(getNameGlobalVar.apply(person)) COMMAND_BLOCK: var name = 'Foo global'; function getNameGlobalFunc(){ console.log(this.name); } const getNameGlobalVar = function(){ console.log(this.name); } const personObjectLiteral = { name: 'Foo object literal', getName: function(){ console.log(this.name); } } function PersonConstructor(){ this.name = 'Foo constructor'; this.setCallback = function( callback ){ this.callback = callback; } this.callCallback = function(){ this.callback(); } function getNameGlobal(){ console.log(this.name); } const getNameArrow = () => { console.log(this.name); }; const getNameBind = getNameGlobal.bind(this); this.getName = function(){ console.log(this.name); } this.getNameReassigned = getNameGlobal; const reasignAsGlobal = this.getName; this.callThisGlobals = function(){ getNameGlobal() reasignAsGlobal(); } this.callThisPersonContext = function(){ getNameArrow(); getNameBind(); this.getName(); this.getNameReassigned(); } } const person = new PersonConstructor(); getNameGlobalFunc(); getNameGlobalVar(); person.callThisGlobals(); personObjectLiteral.getName(); person.callThisPersonContext(); console.log(getNameGlobalVar.call(personObjectLiteral)) console.log(getNameGlobalVar.apply(person)) - using function - using arrow function - Rule 1: Inside a function, this refers to a global context(window for browser, global for nodejs); - Rule 2: Inside a method, refers to an object; - Rule 3: The value of this can be modified by call and apply functions and also when converts a method to a function and vice-versa; - Rule 4: The value of this will never change if is in an arrow function or a bind function, therefore invalidating the rules 1, 2 and 3; - the global variable balance. Line 1 - the variable balance at object account. Line 2 - globalState.js: Initialize global variables, - legacy.js: Access global variables through this. - The line callback.call(thisArg, array[i]) from modifyArray doesn’t change the value of this, look the Rule 4. - The value of this is window because it set the this to the nearest scope, which in this case is window - Inside a function, this refers to a global context(window for browser, global for nodejs); - Inside a method, refers to an object; - The value of this can be modified by call and apply functions and also when converts a method to a function and vice-versa; - The value of this will never change if is in an arrow function or a bind function, therefore invalidating the rules 1, 2 and 3; - If the callback is a bind function or an arrow function, the value of this will never change regardless of how it is called. Rule 4 - if the callback is just a function, the value of this depends on how will be called then could apply the rules 1, 2 or 3. - this is window - declare var outside any function is accessible via this - this doesn't access window - this doesn't access window - this is global - declare var outside any function is NOT accessible via this