this and super in JavaScript
Tags: javascript, computerscience, beginners, tutorial
Published: 2021-02-07 02:30:13


# Prototyping in JavaScript

Before one can understand `this` and `super`, one must understand prototypes. Below is a demystification of how `Object.*` methods work in terms of `__proto__`.

```JavaScript
// !!! DO NOT INSERT THIS INTO YOUR CODE !!! //
Object.setPrototypeOf = function(object, proto) {
    object.__proto__ = proto;
    return object;
};
// !!! DO NOT INSERT THIS INTO YOUR CODE !!! //
Object.getPrototypeOf = function(object) {
    return object.__proto__;
};
// !!! DO NOT INSERT THIS INTO YOUR CODE !!! //
Object.create = function(proto, props) {
    var _object = {__proto__: proto};
    if (props) Object.defineProperties(_object, props);
    return _object;
};
// !!! DO NOT INSERT THIS INTO YOUR CODE !!! //
```

Prototypes work like this: accessing `object.property` searches `object` for `"property"`. If `object` does not have `"property"`, then `object.__proto__` is searched. Then, `object.__proto__.__proto__` is searched. This keeps going until `__proto__` is `null`:

```JavaScript
console.log(Object.prototype.__proto__); // logs `null`
var ap = Array.prototype.__proto__;
console.log(ap === Object.prototype); // logs `true`
```

Below is a demystification of how property lookups are performed. Getting `object.property` will exhibit the same behavior as `propertyLookup(object, "property")` all the time.

```JavaScript
function propertyLookup(_o, prop) {
    var obj = _o;
    do {
        var desc=Object.getOwnPropertyDescriptor(obj, prop);
        if (desc) {
            if (desc.get) return desc.get.call(_o);
            return desc.value; // handles all other cases
        }
    } while (obj = obj.__proto__);
    return undefined; // unneccecary because this is default
}
function propertyAssign(_o, prop, _value) {
    var obj = _o;
    do {
        var desc=Object.getOwnPropertyDescriptor(obj, prop);
        if (desc) {
            if (desc.set) {
                desc.set.call(_o, _value);
                return _value;
            }
            if (desc.get) return _value; // no way to handle
            if (!desc.writable) return _value;//won't handle
            if (obj === _o) { // keep property description
                desc.value = _value;
                Object.defineProperty(obj, prop, desc);
                return _value;
            }
            break; // handles all other cases
        }
    } while (obj = obj.__proto__);
    Object.defineProperty(obj, prop, {
        value: _value,
        writable: true,
        enumerable: true,
        configurable: true
    });
    return _value;
}
```

# `this`

`this` as `that` happens for function `f` in 3 and only 3 circumstance in JavaScript as of 2021:

1. Property access: `that.callWithThat()`, `that["callWithThat"]()`, and `that[0]()` (or any index)
2. Function methods: `f.call(that)`, `f.bind(that)`, `f.apply(that)`, and `Reflect.apply(f, that)`
3. Constructors: `new f` and `Reflect.construct(f, [], that)`

### `that.callWithThat()`

Whenever you access a property you proceed to call, the object you accessed becomes the `this` of the function you called. Observe:

```JavaScript
function method() {
    console.log( this.name );
}
var inner = {
    name: "inner",
    handle: method
};
var outer = {
    name: "outer",
    handle: method,
    inner: inner
};

inner.handle(); // logs "inner"
outer.handle(); // logs "outer"
outer.inner.handle(); // logs "inner"

var handle = outer.handle; // or var { handle } = outer;
handle(); // throws an error because `this` is undefined
```

Note that the prototype of the function does not matter one bit:

```JavaScript
var utils = {
    print: function() {
        console.log( this.value );
    }
};
utils.print.value = "Hello!"
utils.print(); // logs `undefined`
utils.value = "World!";
utils.print(); // logs `World!`
```

However, you can do some interesting trickery using named functions:

```JavaScript
var moldable = {
    setProto: function protoMethod() {
        Object.setPrototypeOf(this, protoMethod);
    },
    printFoob: function() {
        console.log(this.foob);
    }
};
moldable.setProto.foob = 10;
moldable.printFoob(); // logs undefined
moldable.setProto();
moldable.printFoob(); // logs `10`
```

You can also make a non-deterministic object (albeit a very very slow object) via getters:

```JavaScript
var options = [
    {value: 50},
    {value: "dinosaur"},
    {value: true},
    {value: 1e+99}
];
var nondet = {
    get status() {
        Object.setPrototypeOf(this, options[
            Math.random() * options.length | 0
        ]);
        return "OK";
    }
};
console.log(nondet.value); // logs `undefined`
console.log(nondet.status); // logs `OK`
console.log(nondet.value); // logs something random
console.log(nondet.status); // logs `OK`
console.log(nondet.value); // logs something random
```

The same goes with arrays:

```JavaScript
var arr = ["ArrayName", function() {
    console.log( this[0] );
}];
arr[1](); // logs "ArrayName"
```

### 2. Function methods

Function methods manually set the `this` property. Below is the simplest possible demystification of the function methods:

```JavaScript
// !!! DO NOT INSERT THIS INTO YOUR CODE !!! //
Function.prototype.call = function(proto, ...args) {
    proto.__call_method = this; // `this` is a function
    return proto.__call_method(...args);
};
// !!! DO NOT INSERT THIS INTO YOUR CODE !!! //
Function.prototype.bind = function(proto, ...args) {
    var that = this; // `this` is a function
    return function() {
        proto.__call_method = that;
        return proto.__call_method(...args, ...arguments);
    };
};
// !!! DO NOT INSERT THIS INTO YOUR CODE !!! //
Function.prototype.apply = function(proto, argsList) {
    proto.__call_method = this; // `this` is a function
    return proto.__call_method(...argsList);
};
// !!! DO NOT INSERT THIS INTO YOUR CODE !!! //
```

There are many problems with the simplistic demonstration above:

1. A `__call_method` property is left on the `proto` object.
2. The methods don't work when proto is `null` or a primitive
3. The methods don't have the correct function names
4. The `bind` method returns an object with a prototype
5. The `apply` method doesn't work when the arguments list is `null`

For the sake of completeness, below is a standard-compliant demystification of how the function methods work:

```JavaScript
// sliceFrom has very similar behavior to Array.prototype.slice
function sliceFrom(array, start) {
    var result = [];
    for (var k=0, i=start, len=array.length; i < len; k++, i++)
        result[k] = array[i];
    return result;
};
// !!! DO NOT INSERT THIS INTO YOUR CODE !!! //
Function.prototype.call = function call(that) {
    if (that == null) return this(...sliceFrom(arguments, 1));
    var proto=typeof that == "object" ? that : that.__proto__;
    var uuid = Symbol();
    proto[uuid] = this;
    var returnValue = proto[uuid](...sliceFrom(arguments, 1));
    delete proto[uuid];
    return returnValue;
};
// !!! DO NOT INSERT THIS INTO YOUR CODE !!! //
Function.prototype.bind = function bind(that) {
    var f = this, superArgs = sliceFrom(arguments, 1);
    if (that == null) return function() {
        f(...superArgs, ...arguments);
    };
    var proto=typeof that == "object" ? that : that.__proto__;
    var uuid = Symbol();
    proto[uuid] = this;
    function Binded() {
        return proto[uuid](...superArgs, ...arguments);
    };
    Binded.prototype = undefined;
    return Binded;
};
// !!! DO NOT INSERT THIS INTO YOUR CODE !!! //
Function.prototype.apply = function apply(that, _args) {
    var args = _args == null ? [] : _args;
    if (that == null) return this(...args);
    var proto=typeof that == "object" ? that : that.__proto__;
    var uuid = Symbol();
    proto[uuid] = this;
    var returnValue = proto[uuid](...args);
    delete proto[uuid];
    return returnValue;
};
// !!! DO NOT INSERT THIS INTO YOUR CODE !!! //
```

The only two discrepancies from the standard behavior of function methods is the introduction of a symbol upon the `proto`, which can be revealed via `Object.getOwnPropertySymbols` and the fact that the prototype is still `in` the function returned from `Function.prototype.bind`.

### 3. `new` constructors

`new f` sets the value of `this` to be `Object.create(f.prototype)`. This is true for functions and classes alike.

```JavaScript
function constructor() {
    console.log(this.__proto__ === constructor.prototype);
    console.log(Object.getOwnPropertyNames(this).length);
}
new constructor(); // logs `true` and `0`
```

When `f` is a plain old function (not a class), it can return an object, which will become the new value of the `new f`. Observe:

```JavaScript
var refObject = {name: "John"};
function construct() {
    return refObject;
}
console.log(new construct().name) // logs `"john"`
console.log(new construct === new construct); // logs `true`
```

`class`es are mostly just sugar syntax for plain old constructors. Below is how one would mix the two together.

```JavaScript
function Value(initial) {
    this.value = initial;
}
Value.prototype.get = function() {
    return this.value;
};
Value.prototype.set = function(newvalue) {
    this.value = newvalue;
};
class UnsetError extends Error {} // special error type
class MaybeValue extends Value {
    constructor(value, state) {
        super( value );
        this.isset = !!state;
    }
    get() {
        if (!this.isset)
            throw new UnsetError("MaybeValue not ready");
        return super.get();
    }
    set(newvalue) {
        this.isset = true;
        super.set( newvalue );
    }
}
var val = new MaybeValue;
try {
    console.log( val.get() ); // throws error
} catch(e) {
    if (!(e instanceof UnsetError)) throw e; //propagate
    val.set("example value"); // initialize the value
}
console.log( val.get() ); // logs `"example value"`
```

The real power of `class`es comes into play with `extend`ing native constructors:

```JavaScript
class MyArray extends Array {
    constructor() {
        super("hello", ...arguments);
    }
    set(index, value) {
        this[index] = value;
    }
}

var arr = new MyArray;
arr.set(1, 10);
arr.set(2, 20);
console.log( arr.length ); // logs 3, just like a native array
console.log( arr[0] ); // logs `"hello"`
arr.length = 0;
console.log( arr[2] ); // logs `undefined` like a native array
```

In the above example, we extended the `Array` constructor, and our `MyClass` behaved exactly as a native array would. There is no pretty way to achieve this same behavior with old constructors. The solution below is how it would be done, and it's pretty ugly and slow because you have to emulate the behavior of `length` with a getter and setter.

```JavaScript
var lengthSymbol = Symbol("length");
var arrayDescriptor = {
    get: function() {
        var max = this[lengthSymbol];
        for (var key in this)
            if (max < key && Math.floor(key) === +key)
                if (this.hasOwnProperty(key))
                    max = +key;
         return max;
    },
    set: function(_val) {
        var value = +_val;
        if (value < 0 || Math.floor(value) !== value)
            throw RangeError("Invalid array length");
        this[lengthSymbol] = value;
        for (var key in this)
            if (value <= key && Math.floor(key) === +key)
                delete this[key];
    }
};
function extendArray(proto) {
    Object.setPrototypeOf(proto, Array.prototype);
    proto[lengthSymbol] = 0;
    Object.defineProperty(proto, "length", arrayDescriptor);
}
function MyArray() {
    this[0] = "hello";
    for (var k=1, i=0, len=arguments.length; i < len; k++, i++)
        this[k] = arguments[i];
    this[lengthSymbol] = k;
}
extendArray( MyArray.prototype );
MyArray.prototype.set = function(index, value) {
    this[index] = value;
};

var arr = new MyArray;
arr.set(1, 10);
arr.set(2, 20);
console.log( arr.length ); // logs 3, just like a native array
console.log( arr[0] ); // logs `"hello"`
arr.length = 0;
console.log( arr[2] ); // logs `undefined` like a native array
```

# `super`

`super` means exactly the same thing as `this.__proto__` except that `super` is a keyword so JavaScript won't execute (it raises a `SyntaxError`) when `super` is put in the wrong place.

```JavaScript
var object = {
    __proto__: {
        value: [1, 2, 3],
    },
    value: ["a", "b", "c"],
    printThisValue: function() {
        console.log(this.value);
    },
    printSuperValue() {//same as printSuperValue: function()
        console.log(super.value);
    },
    printThisProtoValue: function() {
        console.log(this.__proto__.value);
    }
};
object.printThisValue(); // logs ["a", "b", "c"]
object.printSuperValue(); // logs [1, 2, 3]
object.printThisProtoValue(); // [1, 2, 3], same as super
```

# `new.target` v.s. `this`

They both serve different purposes, but they are both local to the function being called. `new.target` is either `undefined` or a callable function whose `.prototype` property was used to create `this`:

```JavaScript
function printTarget() {
    // Both new.target and this are undefined here
    console.log(new.target);
};
new function c() {
    console.log(new.target === c); // logs `true`
    var p = new.target.prototype; // c.prototype
    console.log(p === this.__proto__); // logs `true`
    printTarget(); // logs `undefined`
};
```

However, without `new`, `new.target` is `undefined` everywhere:

```JavaScript
(function() {
    console.log(this); // logs `[1,2,3]`
    console.log(new.target); // logs `undefined`
}).call([1,2,3])
```
`new.target`, just like `this`, is still visible in local arrow functions, as these arrow functions allow `this` and `new.target` to pass through.

```JavaScript
new function c(){
    this.hello = 427;
    (() => {
        console.log(this.hello);
        console.log(new.target === c);
    })(); // logs `427` and `true`
}
```

`new.target` has the same lifespan as `this` and persists within arrow expressions beyond the scope of the original instantiation. The value of `this` and `new.target` are frozen in time at the location where the arrow function is created.

```JavaScript
(new function c(){
    this.hello = 427;
    return msg => {
        console.log(msg);
        console.log(this.hello);
        console.log(new.target === c);
    };
})("Foob"); // logs `"Foob"`, `427`, and `true`
```


`new.target` is important because, without it, userland functions are unable to determine whether they are supposed to initialize an object.

# Correctly understanding `__proto__`

The very first snippet at the top of this post exhibits wrongful behavior in the following two circumstances:

1. when the object doesn't inherit from `Object.prototype`, for example, `Object.create(null)`
2. When `__proto__` is shadowed (e.x. `Object.defineProperty({}, "__proto__", {value: "test"})` would not change the internal proto and further attempt to read the object's `__proto__` would yield `"test"`)

To see this in action, observe the following code:

```JavaScript
var n = new Number( 47 );
console.log( Object.keys( n ) ); // logs []
console.log( JSON.stringify(n) ); // logs "4.7"
console.log( n.toExponential() ); // logs "4.7e+1"

// This is equivalent to n.__proto__ = n.__proto__
n.__proto__ = Number.prototype;
console.log( Object.keys( n ) ); // logs []
console.log( JSON.stringify(n) ); // logs "4.7"
console.log( n.toExponential() ); // logs "4.7e+1"

n.__proto__ = null;
console.log( Object.keys( n ) ); // logs []
console.log( JSON.stringify(n) ); // Uncaught TypeError: can't convert n to number
console.log( n.toExponential() ); // Uncaught TypeError: n.toExponential is not a function

// Because n no longer inherits from Object.prototype, __proto__ is now an ordinary property
n.__proto__ = Number.prototype;
console.log( Object.keys( n ) ); // logs ["__proto__"]
console.log( JSON.stringify(n) ); // Uncaught TypeError: can't convert n to number
console.log( n.toExponential() ); // Uncaught TypeError: n.toExponential is not a function
```

It errors because `__proto__` is *actually* a non-special ordinary property on `Object.prototype`. Thus, the correct way to describe `__proto__` is to write the getters and setters for `Object.prototype.__proto__` in terms of `Object.getPrototypeOf` and `Object.setPrototypeOf`.

```JavaScript
// !!! DO NOT INSERT THIS INTO YOUR CODE !!! //
Object.defineProperty(Object.prototype, "__proto__", {
    configurable: true,
    enumerable: false,
    get: function() {
        return Object.getPrototypeOf( this );
    },
    set: function(newProto) {
        Object.setPrototypeOf( this, newProto );
    }
});
// !!! DO NOT INSERT THIS INTO YOUR CODE !!! //
```

The above code will *always* exhibit the proper behavior of `__proto__`. See this for yourself by running `Object.getOwnPropertyDescriptor(Object.prototype, "__proto__")` in the console and observing the native getter and setter methods defined there.

That's all. I hope this article helped expand your knowledge of JavaScript's inner workings.