DEV Community

Discussion on: 🧐Most Confusing Part Of JavaScript.

Collapse
 
ats1999 profile image
Rahul kumar • Edited

If you know oops then this is no more confusing for you, but the way we use this in JS is confusing.

Collapse
 
peerreynders profile image
peerreynders • Edited

Approaching JavaScript with a classical object oriented mindset sets you up for failure and familiarity with class-based OO programming sets you up for confusion with 'this' in JavaScript.

Forget about what 'this' means in OOP languages and approach 'this' as an entirely new and independent concept. Remember JavaScript didn't get classes until ES2015 and even then those "classes" aren't "classical":

Quote

Classes are a template for creating objects.

Objects aren't members of the creating class for life (they can be augmented at runtime) and neither do objects even need a class to be created. So while class is more than syntax sugar it's not about classical OOP.

Pre- class (ES5.1 and earlier)
11.1 The 'this' Keyword

The 'this' keyword evaluates to the value of the ThisBinding of the current execution context.

That's it. Nothing about objects and class didn't even exist yet. If you felt compelled to practice classical OO with JS you had to learn about prototype chaining, constructor stealing and combination, prototypal, parasitic and parasitic combination inheritance.

Under 10.3 Execution Contexts

An execution context contains whatever state is necessary to track the execution progress of its associated code.

In practical terms this means that in general inside a function fn() the ThisBinding is determined by the way the function is called:

  • Call the function directly fn() then 'this' is the same as it would be for code at the top-level: undefined in a strict environment or globalThis in a non-strict environment.
  • When a function is invoked via the new operator 'this' refers to the object under construction.
  • Call the function via an object reference e.g. record['fn']() or record.fn() then the object will be bound to 'this'.
    • This mimics a method call but also enables reuse of a function bound to the prototype object for an object with that prototype object.
    • So just copying the function reference from an object property doesn't carry the ThisBinding with it.
  • Both Function.prototype.apply() and Function.prototype.call() give direct dynamic control over the ThisBinding.
  • Function.prototype.bind() creates an entirely new function which has its ThisBinding permanently bound to the specified context.

  • ES2015 introduced arrow function expressions not as a shorthand for function () but as a bind -ing convenience as arrow functions will always bind their 'this' to the 'this' of the context that creates them.

Aside: JavaScript is Function-Oriented

<!DOCTYPE html>
<html>
  <head>
    <title>JavaScript ThisBinding</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
  </head>
  <body>
    <p>Check the console</p>
    <script>
     // non-strict mode
     const record = {
       data: 42,
       fn: showFunctionContext,
     };
     const justData = {
       data: 2021,
     };

     console.log('script global', globalThis); // Window
     console.log('script top-level', this);    // Window
     showFunctionContext();                    // Window
     record['fn']();                           // {data: 42, fn: ƒ}, data 42
     record.fn();                              // {data: 42, fn: ƒ}, data 42
     showFunctionContext.call(justData);       // {data: 2021}, data 2021
     showFunctionContext.apply(justData);      // {data: 2021}, data 2021
     const newFn = showFunctionContext.bind(justData);
     newFn();                                  // {data: 2021}, data 2021
     const arrowOne = makeArrowFunction.apply(undefined);
     arrowOne();                               // Window (i.e. undefined was replaced with globalThis)
     const arrowTwo = makeArrowFunction.apply(justData);
     arrowTwo();                               // {data: 2021}, data 2021

     function showFunctionContext() {
       console.log('function in script', this);
       if (this?.hasOwnProperty('data')) {
         console.log('data', this.data);
       }
     }

     function makeArrowFunction() {
       return () => {
         console.log('arrow function', this);
         if (this?.hasOwnProperty('data')) {
           console.log('data', this.data);
         }
       };
     }
    </script>

    <script type="module">
     const record = {
       data: 42,
       fn: showFunctionContext,
     };
     const justData = {
       data: 2021,
     };

     console.log('module global', globalThis); // Window
     console.log('module top-level', this);    // undefined
     showFunctionContext();                    // undefined
     record['fn']();                           // {data: 42, fn: ƒ}, data 42
     record.fn();                              // {data: 42, fn: ƒ}, data 42
     showFunctionContext.call(justData);       // {data: 2021}, data 2021
     showFunctionContext.apply(justData);      // {data: 2021}, data 2021
     const newFn = showFunctionContext.bind(justData);
     newFn();                                  // {data: 2021}, data 2021
     const arrowOne = makeArrowFunction.apply(undefined);
     arrowOne();                               // undefined
     const arrowTwo = makeArrowFunction.apply(justData);
     arrowTwo();                               // {data: 2021}, data 2021

     function showFunctionContext() {
       console.log('function in module', this);
       if (this?.hasOwnProperty('data')) {
         console.log('data', this.data);
       }
     }

     function makeArrowFunction() {
       return () => {
         console.log('arrow function', this);
         if (this?.hasOwnProperty('data')) {
           console.log('data', this.data);
         }
       };
     }
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode