Skip to content

Instantly share code, notes, and snippets.

@jsnanigans
Last active January 3, 2024 16:24
Show Gist options
  • Save jsnanigans/57485d4694aad3593a29c6e59f01bfd1 to your computer and use it in GitHub Desktop.
Save jsnanigans/57485d4694aad3593a29c6e59f01bfd1 to your computer and use it in GitHub Desktop.
i dont know the java script
const person =  {
	name: 'Brendan',
	bread: true
}

it can be changed/modified

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

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.

const firstName = 'Brendan';
const bread = true;

const person = {
	firstName,
	bread
}

Object literal declaration syntax

Shorthand for functions:

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:

// 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

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:

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:

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:

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:

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:

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.

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:

const person = {
	name: {
		first: 'Yaoo',
		last: 'Juual'
	},
	age: 29
}

let's add a getter to get the person's full name, and a setter:

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:

console.log(person.fullName) // result: "Yaoo Juual"

when we use the setter:

person.fullName = 'Het Faar';

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

Read Next

  • [[Primitive Values]]
  • [[Prototype]]

#published #javascript

Based on

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:

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__:

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:

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:

// 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:

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.

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.

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.

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]]

Based on

  • [[Prototype]]

Every prototype, has itself a __proto__ property pointing to its [[Constructor Function]] prototype.

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]].

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:

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:

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:

merlot; // Grape{ makesGoodWine: true }

So let's call Fruit form within Grape with the .call method.

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:

...
	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:

let merlot = new Grap('merlot', true, true);

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

merlot.askIfEddible(); // "merlot is eddible!"

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 `{}``

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:

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
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment