Skip to content

Instantly share code, notes, and snippets.

@jsnanigans
Last active January 3, 2024 16:24
Show Gist options
  • Select an option

  • Save jsnanigans/57485d4694aad3593a29c6e59f01bfd1 to your computer and use it in GitHub Desktop.

Select an option

Save jsnanigans/57485d4694aad3593a29c6e59f01bfd1 to your computer and use it in GitHub Desktop.

Revisions

  1. jsnanigans revised this gist Jan 3, 2024. 1 changed file with 61 additions and 0 deletions.
    61 changes: 61 additions & 0 deletions 04_iterator.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,61 @@
    Introduced in [[ES6/ES2015]]

    Stop iterations with `break`

    ## What is an iterable?
    [[Object]] that allows iterating over itself.

    > it can be enumerated with a `for..of` loop
    > it adheres to the iterator protocol
    > returns an object with at least a `value` and a `done` property
    an iterator must implement the `@@iterator` method / the `Symbol.iterator` a [[Well Known Symbol]]

    `for..of` iterates over values, `for..in` iterators over enumerable properties, for example the keys of an `{}``

    ```ts
    array = [1,2,3];
    it = array[Symbol.iterator]()
    it.next() // would return value 1
    it.next() // would return value 2
    it.next() // would return value 3
    it.next() // would return value undefined, done: true
    ```

    `it.next()` will always call the next iterator and will return `{value: any, done: boolean}`

    `for..of` loops call `Symbol.iterator` under the hood

    the `done` will only be set to true after the last item in the iterable object, when `done` is true, the `value` is most likely `undefined`


    ## Other iterator method

    `next`, explained above
    `return`, returns done, ends the loop
    `throw`, returns done, logs error

    ### Custom iterators

    example:
    ```ts
    data = [2,4,6,7,10];
    let idx=0;
    const mIterable = {
    [Symbol.iterator]() {
    return this;
    },
    next() {
    const current = data[idx];
    idx++;
    return {
    value: current,
    done: Boolean(current) ? false : true
    }
    }
    }

    for (let val of mIterable) {
    console.log(val) // 2,4,6,7,10
    }
    ```
  2. jsnanigans revised this gist Jan 3, 2024. 3 changed files with 0 additions and 0 deletions.
    File renamed without changes.
    File renamed without changes.
  3. jsnanigans revised this gist Jan 3, 2024. 3 changed files with 238 additions and 0 deletions.
    File renamed without changes.
    100 changes: 100 additions & 0 deletions prototypal_inheritence.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,100 @@
    ### Based on
    - [[Prototype]]

    Every prototype, has itself a `__proto__` property pointing to its [[Constructor Function]] prototype.
    ```ts
    Object.__proto__ // {}

    function Fruit(name) {
    this.name = name;
    }
    Fruit.prototype.size = 2;

    const apple = new Fruit('apple');

    apple.__proto__ // { size: 2 }


    apple.__proto__.__proto__ // {}
    apple.__proto__.__proto__.__proto__ // null
    ```

    The `apple.__proto__.__proto__` is the original `Object.__proto__`, which is the root of all prototypes, so its `__proto__` attribute is `null`.

    This is known as the **Prototype Chain**.

    ## Make your own Prototypal Inheritance
    This might be useful when we want to inherit some properties for a new [[Constructor Function]].

    ```ts
    function Fruit(name, eddible) {
    this.name = name;
    this.eddible = eddible;
    Object.defineProperty(this, 'askIfEddible', {
    get: function() {
    if (this.eddible) {
    return `${this.name} is eddible!`;
    } else {
    return `${this.name} is not eddible, do not eat me!`;
    }
    }
    })
    }


    function Grape(name, eddible, makesGoodWine) {
    this.makesGoodWine = makesGoodWine;
    }

    ```

    We want Grape to have all the useful methods and properties from `Fruit`. Remember that we can inherit properties from the [[Constructor Function]] prototype, and we can chain prototypes.

    So to achieve this, we want to see the `Grape` prototype to the `Fruit` prototype:
    ```ts
    Grape.prototype = Object.create(Person.prototype);
    Grape.prototype.constructor = Grape;
    ```
    We use `Object.create` here, instead of `new` because we do not want to change the definition of the `this` keyword in Person.

    The second line is required to re-set the `Greap` prototypes `constructor`:
    This is required because the Grape `Grape.prototype.constructor` would otherwise now use the `Person.prototype.constructor`.

    Now when we create a new `Grape`, and take a look at the prototype chain, we see that it is chained in the way that we wanted:
    ```ts
    let merlot = new Grape('merlot', true, true)
    merlot.__proto__ // Grape{}
    merlot.__proto__.__proto__ // Fruit{}
    ```

    Great! But we are not done yet!
    We still need to use the parameters. That we passed into the `Grape` constructor, to achieve this we need to call `Fruit` from inside `Grape`. If we don't, then our `Grape` will not have the useful properties of `Fruit`, like `askIfEddible`:
    ```ts
    merlot; // Grape{ makesGoodWine: true }
    ```

    So let's call `Fruit` form within `Grape` with the `.call` method.
    ```ts
    function Grape(name, eddible, makesGoodWine) {
    Fruit.call(this, name, eddible);
    this.makesGoodWine = makesGoodWine;
    }
    ```

    The `.call` method allows us to pass the definition of `this` for whatever we are calling, so when `Fruit` runs, and sets its properties:
    ```ts
    ...
    this.name = name;
    this.eddible = eddible;
    ...
    ```
    It is actually using the `this` from our `Grape` object.

    Now we have access to all methods and properties:
    ```ts
    let merlot = new Grap('merlot', true, true);

    merlot; // Grape{ makesGoodWine: true, eddible: true, name: 'merlot', .. }

    merlot.askIfEddible(); // "merlot is eddible!"
    ```
    138 changes: 138 additions & 0 deletions prototype.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,138 @@
    #published #javascript

    ### Based on
    - [[Object]]
    - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object


    Every [[Object]] in JS has a `prorotype`
    ## Functions

    > A function’s prototype is the object **instance** that will become the prototype for all objects created using this function as a constructor.
    For functions, you can find them in `.prototype`:
    ```ts
    let fn = function() {}
    console.log(fn.prototype) // {}
    ```


    ## Objects

    >An Object's prototype is the object **instance** from which the object is inherited.
    For all other [[Objects]], the prototype is accessible through `.__proto__`:
    ```ts
    let obj = {name: 'Hena'}
    console.log(obj.prototype) // undefined
    console.log(obj.__proto__) // {}
    ```


    # Inheritance

    The `__proto__` property from the object, is a reference to the `prototype` property on the function, they are, in fact, the same. If you change one, all objects prototype will change for all objects and the [[Constructor Function]].

    ### Prototype Property vs. Property
    In this example, the person [[Constructor Function]] has a Prototype Property called `age`, this allows us to access `.age` on each of the objects created from that Constructor:
    ```ts
    function Person(name) {
    this.name = name
    }

    Person.prototype.age = 34

    let jim = new Person('jim');
    let rustik = new Person('rustik');

    jim.age // 34
    rustik.age // 34
    ```

    If we change the prototype age property for any of the Person objects, all will change:
    ```ts
    // either
    jim.__proto__.age = 28
    // or
    Person.prototype.age = 28

    jim.age // 28
    rustik.age // 28
    ```

    When we access the `.age` prop on a person object, and the object does not have a `age` property of its own, then if the prototype has an `age` property, the `person.age` property will act like a getter for the prototype property value.

    We can check if the person has their own `age` property with the `hasOwnProperty` method:
    ```ts
    function Person(name) {
    this.name = name
    }

    Person.prototype.age = 28

    let jim = new Person('jim');

    jim.hasOwnProperty('age') // false
    jim.age // 28
    ```

    We can assign an `age` to one of the Person objects, this will then be scoped to that object. This will not change the prototype property or the age property on other objects with the same prototype.

    ```ts
    function Person(name) {
    this.name = name
    }

    Person.prototype.age = 28

    let jim = new Person('jim');
    let rustik = new Person('rustik');

    jim.age = 35

    jim.hasOwnProperty('age') // true
    jim.age // 35
    jim.__proto__.age // 28
    rustik.age // 28
    ```


    ## Changing the Function Prototype
    Its important to remember that the `__proto__` prop is set on the object at the time it is created. So if we change the `prototype` prop of the [[Constructor Function]] after we created an object from it, the `__proto__` prop will still reference the original prototype object.

    ```ts
    function Person(name) {
    this.name = name
    }
    Person.prototype.age = 28
    let rustik = new Person('rustik');
    Person.prototype = { age: 12 }
    let zee = new Person('zee');

    rustik.age // 28
    zee.age // 12
    ```

    Here we created a new object `{ age: 12 }` and set the `Person.prototype` reference to that new object. From now on, all new `Person` objects will have the `__proto__` property point to our new object.

    That means, that if we change the `__proto__.age` prop on the new Person "zee", this will not affect the Person objects we created before.
    ```ts
    function Person(name) {
    this.name = name
    }
    Person.prototype.age = 28
    let rustik = new Person('rustik');
    Person.prototype = { age: 12 }
    let zee = new Person('zee');

    zee.__proto__.age = 14;

    l.og = rustik.age // 28
    l.og = zee.age // 14
    ```

    By changing to which object the `Person.prototype` prop points, we have essentially cut the connection between the `Person` [[Constructor Function]] and the objects that were created before the change.

    However, all `Person` objects created before the change still all have the same `__proto__` object reference.
    # Go deeper:
    - [[Prototypal Inheritance]]
  4. jsnanigans created this gist Jan 3, 2024.
    216 changes: 216 additions & 0 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,216 @@
    ```ts
    const person = {
    name: 'Brendan',
    bread: true
    }
    ```

    it can be changed/modified
    ```ts
    person.jam = true
    ```

    Thanks to the dynamic nature of JS we don't need any ceremonious ways to add `jam` even though it was not in the original definition of the object, the tradeoff is that we do not get any type-checking.

    ## Functions on the object
    ```ts
    person.getIngedients = function() {
    return { jam: this.jam, bread: this.bread }
    }
    ```

    if an object property is not a value, but a function, we call it a **method.**

    the keyword `this` is used to reference other properties within the object

    ### Shorthand syntax

    Sets the object key to the variable name.
    ```ts
    const firstName = 'Brendan';
    const bread = true;

    const person = {
    firstName,
    bread
    }
    ```

    #### Object literal declaration syntax
    Shorthand for functions:
    ```ts
    const person = {
    value: 22,
    traditional: function() { return this.value },
    shorthand() { return this.value }
    }
    ```

    ### Functions in the built-in native object `Object`

    `Object.keys(myObject)`: Returns an array of all keys. The `for..in` loop, will loop over the keys or "property names"

    `Object.assign(ob1, ob2)`: Copies properties from `ob2` into `ob1`, mutates `obj1`.

    ### JavaScript Equality
    `==`
    it's not type safe, will equate `"42" == 42` as true. It should be avoided unless you know exactly what you are doing.

    `===`
    Type-safe, compares the memory address

    `Object.is()`
    Almost the same as `===`, but has some cases where it gives a different result:
    `NaN equals NaN`
    `+0 not equal -0`

    When comparing two objects, the memory address is compared for all the equality checks above, for any JS [[Primitive]], the actual value is compared.


    [[Constructor Function]]


    ### Onject.create() and Object Literals

    These two objects are essentially identical:
    ```ts
    // Object Literal
    let personA = {
    firstName: 'Kate',
    lastName: 'Tobo',
    age: 42
    }

    // Object constructor function
    let personB = Object.create(
    Object.prototype,
    {
    firstName: {value: 'Kate', enumerable: true, writable: true, configurable: true},
    lastName: {value: 'Tobo', enumerable: true, writable: true, configurable: true},
    firstName: {value: 42, enumerable: true, writable: true, configurable: true},
    }
    )
    ```



    # Object Properties

    #### Bracket Notation
    ```ts
    const a1cKey = 'user-a1c'

    const person = {
    name: 'Hulu',
    ['height']: 174,
    [a1cKey]: 7
    }

    person['person age'] = 32;
    console.log(person[a1cKey])
    ```

    This allows us to define keys that would otherwise be invalid, for example with spaces. Or, more useful: you can use variables as keys, this comes in especially handy when checking for properties in a loop:

    ```ts
    for (let propName in person) {
    person['computed-key-' + propName] = person[propName];
    }
    ```


    A property is more than just a name and a value, you can see the guts of the property by doing:
    ```ts
    const guts = Object.getOwnPropertyDescriptor(person, 'name');
    // Object {
    // value: "Hulu",
    // writable: true,
    // enumerable: true,
    // configurable: true,
    // }
    ```

    these are the same things we had to set when using `Object.create`

    #### writable
    determines if the value can be modified, you can change the `writable` attribute like this:
    ```ts
    Object.defineProperty(person, 'name', {writable: false});
    ```

    Now when you try to change the `person.name`, the JS runtime will throw an error.

    If the `name` property is an object, and not a primitive, you could still change the properties on the name object `person.name.xx`.

    To prevent the whole object from being changed, you can use `Object.freeze`:
    ```ts
    person.name = { first: 'Hulu', last: 'Van Damme' }
    Object.freeze(person.name);
    ```
    #### enumerable
    by default all properties on an object are enumerable, that means they will show up in a `for..in` loop

    if we change that:
    ```ts
    Object.defineProperty(person, 'name', {enumerable: false});
    ```
    the `name` object will not show up when enumerating over the object, it will also not show up in `Object.keys(person)`

    Finally it will also be hidden when serializing the object, for example with `JSON.stringify(person)`.

    #### configurable
    prevents the property descriptors of being changed, it also prevents the property from being deleted from the object.
    ```ts
    Object.defineProperty(person, 'name', {configurable: false});
    ```

    Now if we try to change the `enumerable` or property to false, like above, an error will be thrown. You also cannot set the `configurable` property back to true again.
    You can still change the `writable` descriptor.
    `delete person.name;` would also cause an error if `configurable` is false.


    ## Property Getters and Setters
    a **getter** allows you to write a function that returns a value, a **setter** allows you to set a value with a function, they are, however, used as if they were normal properties on the object.

    You need to use `Object.defineProperty` to add getters and setters:
    ```ts
    const person = {
    name: {
    first: 'Yaoo',
    last: 'Juual'
    },
    age: 29
    }
    ```

    let's add a getter to get the person's full name, and a setter:
    ```ts
    Object.defineProperty(person, 'fullName', {
    get: function() {
    return this.name.first + ' ' + this.name.last;
    },
    set: function(value) {
    var parts = value.split(' ');
    this.name.first = parts[0];
    this.name.last = parts[1];
    }
    })
    ```

    now we can get the full name with just using the `fullName` property:
    ```ts
    console.log(person.fullName) // result: "Yaoo Juual"
    ```

    when we use the setter:
    ```ts
    person.fullName = 'Het Faar';

    console.log(person.name.first) // result: Het
    console.log(person.name.last) // result: Faar
    ```


    ### Read Next
    - [[Primitive Values]]
    - [[Prototype]]