# All about prototypes

### Prototype inheritance

Every object have a special hidden property `[[Prototype]]` it is either `null` or a reference to another object.

Whenever you read a property from `object` and if it is missing, JavaScript will automatically take it from the prototype.

##### Setting prototype

One of the way to set the prototype is to use the special name `__proto__`:

```javascript
let animal = {
	eat: true
};

let rabbit = {
	jump: true
};

rabbit.__proto__ = animal;

console.log(rabbit.eat); // true, taken from the prototype animal
console.log(rabbit.jump); // true
```

If `animal` has any useful method, then since it is a prototype to the `rabbit` it is able to call it directly as well!

```javascript
let animal = {
  eats: true,
  walk() {
    console.log("Walking");
  }
};

let rabbit = {
  jumps: true,
  __proto__: animal // Another way of setting the prototype
};

// walk is taken from the prototype
rabbit.walk(); // Walking
```

##### Limitation  


The only rules on assigning prototype is that there should be no circular references, and the value of `__proto__` should be either an object or `null` all other types are simply ignored and have no effect.

##### Modifying prototype state

What if we did something like this?

```javascript
let user = {
	name: "Ricky"
    last: "Lu"
    
    get fullName() {
    	return this.name + " " + this.last;
	}
    
    set fullName(value) {
    	[this.name, this.last] = value.split(" ");
	}
};

let admin = {
	__proto__: user,
    isAdmin: true
};

console.log(admin.fullName); // Ricky Lu, this works as expected

admin.fullName = "Another person"; // Now what happens to user.name and user.last?

admin.fullName // Another perons
user.fullName // Ricky Lu, it stays! The state of user is protected
```

<p class="callout info">As you can see if you tried to modify the admin's name by calling the prototype's setter function the state change will be enforced on `admin` and not `user`! Even though you are calling `user`'s setter method. This is because `this` always binds to the object that it is called on, and in this case, it is assigning `admin.name` and `admin.last` to be `"Another"` and `"person"` respectively. `user` is unchanged.</p>

Even if you do something like this:

```javascript
let user = {
	name: "Ricky"
    last: "Lu"
};

let admin = {};

admin.name = "Another";
admin.last = "person";

user -> Will still be "Ricky Lu"
admin -> Will be "Another person", because it is assigning name and last property to admin
```

This behavior is needed because you wouldn't want to modify the prototype's state if multiple object has it as it's prototype, the changes will be untraceable and should be on the object itself not prototype!

##### for...in loop

Using `for...in` it will iterate over the inherited properties as well if it is enumerable, however, `Object.keys(obj)` will only return the keys this object has itself, not inherited ones:

```javascript
let animal = {
  eats: true
};

let rabbit = {
  jumps: true,
  __proto__: animal
};

// Object.keys only returns own keys
console.log(Object.keys(rabbit)); // jumps

// for..in loops over both own and inherited keys
for(let prop in rabbit) console.log(prop); // jumps, then eats
```

### Native prototypes

These are the built-in prototypes that all of the objects inherits from.

##### Object.prototype

This is the default prototype that any object created inherits from. And itself's prototype is just `null` because there is no one above it.

This object has some default method that all object contains `toString, hasOwnProperty,...`

##### hasOwnProperty(prop)

Return `true/false` depending on whether or not the object has that particular property itself, and not inherited.

##### Array.prototype/Function.prototype/Number.prototype

These are the other prototypes that some of the objects that you create inherits from such as Arrays, Functions, and Numbers.

You can verify that they indeed are those prototype by comparing say `[1, 2].__proto__ == Array.prototype` it will be equal to `true`.

[![image.png](https://wiki.tamarinne.me/uploads/images/gallery/2022-12/scaled-1680-/T2simage.png)](https://wiki.tamarine.me/uploads/images/gallery/2022-12/T2simage.png)

##### Primitives

Primitives aren't objects, but somehow we are able to access methods on them, how does that work? Well when you try to access their methods a temporary wrapper objects are created using the built-in constructors `String, Number, Boolean`. They provide the methods and then disappear.

The process of creation are invisible to us and engines optimize them out mostly.

`null` and `undefined` have no object wrapper, so they have no methods or properties.

<p class="callout info">One of the interesting thing that you can do with primitive prototypes is that you can add some custom properties or method that can be used by all primitives. `String.prototype.foo = 5;` will allow all strings to access a foo property.   
`let x = 5;` you can do `x.foo` to get 5.</p>

### Interesting aside

If you set a property to an object and marked it as `enumerable: false`, and you `console.log` the object, that property will not show up because it is not iterated over using `for...in` loop!

### F.prototype  


When writing constructor functions, i.e. functions that are called with `new F()`, where `F` is a constructor function we can take advantage and use `F.prototype` to do prototype inheritance as well.

If we assigned an object to `F.prototype` then when `new F()` is invoked, the object that is created will have its `__prototype__` pointed to the object that we have assigned. So it is another way that we can do prototype inheritance, instead of setting it manually after the object is created using `{...}`.

```javascript
let animal = {
	eat: true
};

function Rabbit(name) {
	this.name = name;
}

Rabbit.prototype = animal;

let rabbit = new Rabbit("White"); // rabbit.__proto__ == animal

console.log(rabbit.eat); // true
```

<p class="callout info">Keep in mind that `F.prototype` is only used when `new F()` is called, so if you decide to change it prototype after you created the object, future object created using the constructor function will have the newer prototype, but existing object keep the old one.  
</p>

This only concerns with constructor methods, nothing else.

### Modern way of setting \[\[prototype\]\]  


`.__proto__` is an old way of letting you access and set `[[prototype]]` and its usage is generally discourage.

The modern way of setting `[[prototype]]` is to use the methods:

- `Object.getPrototypeOf(obj)`: This returns the prototype of the object, same as doing `.__proto__` getter
- `Object.setPrototypeOf(obj, proto)`: This sets the prototype of the `obj` to be `proto`, same as doing `.__proto__` setter
- `Object.create(proto, [descriptors])`: Allows you create an object with the specified prototype and optionally descriptors, this is the same as doing `{__proto__: ... }`

##### True dictionary

The bad usage of `.__proto__` becomes apparent if we decides to keep a key-value pair mapping using object. If we allow the user to enter any kind of key-value pair mapping and if they decides to enter `__proto__` and map to say `5`, it would be invalidated, because using `__proto__` you can only assign it another object or `null`. This isn't the behavior that a dictionary would want right? To fix this we can use `Map` or a real empty object, an object with no inherited prototype to begin with to inherit the setter and getter for `__proto__` via `Object.create(null)`.

Now we are able to do:

```javascript
let empty = Object.create(null); // No prototype inherited
empty.__proto__ = 5;
console.log(empty.__proto__); // 5
```