Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
1358 lines (945 sloc) 49.4 KB
Ch.1: This or that?
  • the this mechanism provides an elegant way of 'passing along' an object reference, leading to cleaner API designer and easier re-use. when we get into objects and prototypes, you'll see the helpfulness of a collection of functions being able to automatically reference the proper context object.
Confusions
  • common misunderstandings
    • 'this refers to the fn itself.' - why would you wanna refer to a fn from inside itself. recursion is a good example of this.
    • 'this refers to the function's scope.' - tricky question, because there is some truth in this. this doesnt refer to a fn's lexical scope. look at this example:
function foo() {
	var a = 2;
	this.bar();
}

function bar() {
	console.log( this.a );
}

foo(); //undefined

it tries to access the scope but really cant. you could've just used bar() instead of this.bar().

What 'this' really is
  • this is not an author-time binding but a runtime binding. it's contextual ased on the conditions of the fn's invocation. this binding has nothing to do with where a fn is declared, but has everything to do with the manner in which its called.

when a fn is invoked, an activation record, also known as an 'execution context', is created. This record contains info about where the function was called from (the call-stack), how the function was invoked, what params were passed, etc.

TL;DR: this is actually a binding that is made when a function is invoked, and what it references is determined entirely by the call-site where the function is called.

Ch.2: `this` all makes sense now!
  • in ch.1 we learned that this is based not around where it is declared but based entirely around its call site.

  • call site = generally 'go locate where a fn is called from', but it not always that easy, as some coding patterns can obscure the true call site. what's important to think about is the call-stack.

  • call stack = the stack of functions that have been called to get us to the current moment in execution. The call-site we care about is in the invocation before the currently executing fn.

EX: (take a look at call-stack and call-site)...

function baz() {
    // call-stack is: `baz`
    // so, our call-site is in the global scope

    console.log( "baz" );
    bar(); // <-- call-site for `bar`
}

function bar() {
    // call-stack is: `baz` -> `bar`
    // so, our call-site is in `baz`

    console.log( "bar" );
    foo(); // <-- call-site for `foo`
}

function foo() {
    // call-stack is: `baz` -> `bar` -> `foo`
    // so, our call-site is in `bar`

    console.log( "foo" );
}

baz(); // <-- call-site for `baz`
  • ah, this makes sense. this doesn't always refer to 'where was the fn called from' because we cant always trust that. what we can trust is the call-site, which is the location in code where a fn is called. the 'call-site' helps us understand what this is a reference to. looking at the call stack above (a stack of fns executed) we can see that the call-ite is in the invokation before the currently executing fn.

  • now we can do this in our heads, but a much easier thing to do is use browser devtools to set a breakpoint in the 1st line of foo(). the debugger will then pause at this line, and show you the call stack. if you're trying to find the call-site that this is referring to, its always the second item from the top.

a bunch of rules.

lets find out HOW the call-site determines where this will point during the execution of a function.

you have to inspect the call-site and determine which of these 4 rules apply:

Default Binding

look at this example:

function foo() {
   console.log( this.a );
}

var a = 2;

foo(); // 2

^ this.a and var a refer to the same thing. this in this regard is in the global scope, which is considered the default binding. if ;'strict mode' is in effect, the global object isn't eligible for default binding, so this will be set to undefined.

Implicit Binding

  • rule 2: does the call-site have a context object?

look at this:

function foo() {
	console.log( this.a );
}

var obj = {
	a: 2,
	foo: foo
};

obj.foo(); // 2

foo() is declared and then a reference to it is passed in obj. now obj isn't really 'owned' or 'contained' by the obj object. but the call-site USES the obj context to reference the function, so you could say that obj 'owns' the function reference at the time the function is called.

  • basically at the point foo() is called, its preceeded by an object reference to obj. **When there is a context object for a function reference, the IMPLICIT BINDING rule says that its THAT object which should be used for the function call's this binding.

^ in other words, obj is the this for the foo() call, so this.a is synonymous with obj.a

Implicitly Lost

A common frustration that this binding creates is when you implicitly bind a fn, and it loses that binding, usually defaulting to the default binding of either the global object or undefined depending on strict mode.

Take this for example:

function foo() {
	console.log( this.a );
}

var obj = {
	a: 2,
	foo: foo
};

var bar = obj.foo; // function reference/alias!

var a = "oops, global"; // `a` also property on global object

bar(); // "oops, global"

^ as you see here, this.a loses its meaning. bar appears to reference obj.foo, but its really just a reference to foo() itself. also, remember, the call-site matters and since bar() is an undecorated call, the default binding applies.

a more common way this loses its binding is in callback scenarios:

function foo() {
	console.log( this.a );
}

function doFoo(fn) {
	// `fn` is just another reference to `foo`

	fn(); // <-- call-site!
}

var obj = {
	a: 2,
	foo: foo
};

var a = "oops, global"; // `a` also property on global object

doFoo( obj.foo ); // "oops, global"

^ same result.

this is changed unexpectedly, you're not really in control of how your callback fn reference will be executed, so have no way (yet) of controlling the call-site to give your intended binding. we'll see a way shortly of fixing this by fixing this.

Explicit Binding

now with implicit binding as we saw above, we had to mutate the object in question to include a ref on itself to the fn, and use this property function reference to indirectly (or implicitly) bind this to the object.

but what if you wanna force a fn call to use a particular object for the this binding without putting a property fn reference on the object?

the prototype comes into play!

all fns have utilities available to them in their prototype, and we'll be talking specifically about call() and apply().

how do they work? as their first param, they take an object to use for the this and then invoke the fn with that this specified. Since you're directly stating what you want this to be, we call it EXPLICIT BINDING. EX:

function foo() {
	console.log( this.a );
}

var obj = {
	a: 2
};

foo.call( obj ); // 2

in my words: we're using call() to say,"hey call obj anytime you are trying to reference this. it basically explicitly sets this to something you specify.

  • unfortunately, EXPLICIT BINDING alone still doesnt offer a solution to the issue previous, of a fn 'losing' its intended this binding, but a variation pattern around explicit binding does the trick...

Hard Binding

function foo() {
	console.log( this.a );
}

var obj = {
	a: 2
};

var bar = function() {
	foo.call( obj );
};

bar(); // 2
setTimeout( bar, 100 ); // 2

// `bar` hard binds `foo`'s `this` to `obj`
// so that it cannot be overriden
bar.call( window ); // 2

^ we create bar() whihch internally calls foo.call(obj), forcing foo with obj binding for this. so no matter how you invoke bar later on, it will always manually invoke foo with obj. This binding is both explicit and strong, so we call it hard binding.

The most typical way to wrap a fn with hard binding creates a pass-thru of any arguments passed and any return value receieved. EX:

function foo(something) {
	console.log( this.a, something );
	return this.a + something;
}

var obj = {
	a: 2
};

var bar = function() {
	return foo.apply( obj, arguments );
};

var b = bar( 3 ); // 2 3
console.log( b ); // 5

you can also use a re-usable helper to express this pattern:

function foo(something) {
	console.log( this.a, something );
	return this.a + something;
}

// simple `bind` helper
function bind(fn, obj) {
	return function() {
		return fn.apply( obj, arguments );
	};
}

var obj = {
	a: 2
};

var bar = bind( foo, obj );

var b = bar( 3 ); // 2 3
console.log( b ); // 5

^ now this, to me, is clearer. we create a function which takes in both a fn, and an object, and binds the fn's this context to the object using apply().

clean and simple.

  • hard binding is a common pattern, so its provided with a built-in ES5 utility: function.prototype.bind EX:
function foo(something) {
	console.log( this.a, something );
	return this.a + something;
}

var obj = {
	a: 2
};

var bar = foo.bind( obj );

var b = bar( 3 ); // 2 3
console.log( b ); // 5

NIIIIICE!!! bind() returns a new fn thats hard-coded to call the original fn with the this context set as you specified.

API Call 'contexts'

many js libraries fns and new built-in JS fns provide an optional param, usually called 'context' designed as work around so you dont have to use bind() to ensure your callback uses a particular this:

function foo(el) {
	console.log( el, this.id );
}

var obj = {
	id: "awesome"
};

// use `obj` as `this` for `foo(..)` calls
[1, 2, 3].forEach( foo, obj ); // 1 awesome  2 awesome  3 awesome

^ internally these fns use explicit binding via call() or apply() saving you the trouble.

the 'new' binding

In class-oriented languages, constructors are special methods attached to a class, that when the class is instantiated, with a new operator, the constructor of that class is called like something = new MyClass(...);

  • JS has a new operator and most ppl think its doing something similar to those class-oriented languages. its not. lets talk about what it actually does...
  • whats a constructor really?
  • constructors are just fns that happen to have the new operator in front of them. they're just fns that are hijacked by the use of new in their invocation.

pretty much any fn, including built-in fns like Number() can be called with new in front of it, and that basically makes the fn call a constructor call. Theres actually no such thing as 'constructor functions', but rather construction calls of of functions.

  • when a fn is invoked with new in front of it, here's what happens automatically:
    1. a new obj is created out of thin air.
    2. *the newly constructed obj is [[Prototype]]-linked
    3. the newly constructed object is set as the this binding for that fn call
    4. unless the fn returns its own alternate object, the new-invoked fn call will automatically return the newly constructed object.

take a look at this:

function foo(a) {
	this.a = a;
}

var bar = new foo( 2 );
console.log( bar.a ); // 2

^ by calling foo() with new in front, we've constructed a new object and set that new object the this for the call of foo()...so new is the final way that a function call's this can be bound. we'll call this new binding.

Everything in order

so we've uncovered 4 rules for binding this for fn calls. ALL you need to do is find the call-site and inspect it to see which rule applies. but what if there's multiple rules (implicit binding + explicit binding, for example)...which takes precedence?

precedence (in order of lowest priority to highest):

  1. default binding
  2. implicit binding
  3. explicit binding

take a look at implicit vs. explicit:

function foo() {
	console.log( this.a );
}

var obj1 = {
	a: 2,
	foo: foo
};

var obj2 = {
	a: 3,
	foo: foo
};

obj1.foo(); // 2
obj2.foo(); // 3    // < -- IMPLICIT

obj1.foo.call( obj2 ); // 3
obj2.foo.call( obj1 ); // 2  // < -- EXPLICIT

now lets check which takes precendence, hard binding or new binding:

function foo(something) {
	this.a = something;
}

var obj1 = {};

var bar = foo.bind( obj1 );
bar( 2 );
console.log( obj1.a ); // 2

var baz = new bar( 3 );
console.log( obj1.a ); // 2
console.log( baz.a ); // 3

^ so bar hard bound against obj, but new bar(3) didnt change obj1.a to be 3 as we would think. instead the hard-bound (to obj1) call to bar() IS able to be overridden with new. since new was applied, we got the newly created object back, which we named baz, and we see that baz.a has the value of 3.

  • NOTE: (review lines 511-569, it goes into the weeds on how new binding overrides work and I got lost)..

Essentially, JS determines whether or not the hard-bound fn has been called with new (resulting in a newly constructed object being its this), and if so, it uses that newly created this rather than the previously specified hard binding for this.

so new binding takes precedence over hard binding. why is this overriding capability useful? The primary reason for this behavior is to create a fn (that can be used with new for constructing objects) that essentially ignores this hard binding but which presets some or all of the fn's arguments.

in my words: new binding takes precedence over hard binding so when we create new instances, we can provide new arguments as needed.

How to determine this

  1. is the fn called with new (new binding)? if so, this is the newly constructed object. var bar = new foo()

  2. Is the fn called with call or apply (explicit binding), even hidden inside a bind hard binding? if so, this is the explicitly specified object.

var bar = foo.call( obj2 )

  1. Is the fn called with a context (implicit binding), otherwise known as an owning object? if so, this is that context object. var bar = obj1.foo() < -- in this case, foo is implicitly bound to obj1.

  2. otherwise, default the this(default binding). if in strict mode, its undefined, otherwise its the global object.

var bar = foo()

Thats just about all there is to understanding the rules of this binding for normal fn calls. almost.

:(

Binding Exceptions

As usual, there are some exceptions to the rules.

Ignored this

if you pass null or undefined as the this binding parameter to call, apply, or bind those values are ignored and the default binding rule applies to the invocation.

EX:

function foo() {
	console.log( this.a );
}

var a = 2;

foo.call( null ); // 2

^ but why would you do something like this? why would you bind foo() to null in the 1st place? according to this book, its common to use apply for spreading out arrays of values as params to a fn call (though I believe you could just use the spread (..) operator).

Safer this

perhaps a safer practice is to pass a specifically set up object for this which is guaranteed not to have side effect in your program. using a military analogy, we'll call this a DMZ (de-militarized zone) object, just an empty and non-delegated object.

if we always pass a DMZ object for ignored this bindings we dont think we need to care about, we'll be sure that any hidden or unexpected usage of this will always be restricted to the empty object, which insulates our program's global object from side effects.

the easiest way to create a totally empty DMZ object is Object.create(null). It's similar to { } but without delegation to Object.prototype so its even 'more empty' than just { }.

ex:

function foo(a,b) {
	console.log( "a:" + a + ", b:" + b );
}

// our DMZ empty object
var ø = Object.create( null );

// spreading out array as parameters
foo.apply( ø, [2, 3] ); // a:2, b:3

// currying with `bind(..)`
var bar = foo.bind( ø, 2 );
bar( 3 ); // a:2, b:3

^ Im lost and dont understand this.

Indirection

one thing to be aware of is you may accidentally create 'indirect references' to fns, and in those cases, the default binding rule applies. one of the most common ways indirect references occur is from an assignment: EX:

function foo() {
	console.log( this.a );
}

var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };

o.foo(); // 3
(p.foo = o.foo)(); // 2

^ the result value of the assignment expression p.foo = o.foo is a reference to just the underlying fn object. as such, the effective call-site is just foo(), not p.foo() as you might expect. as such, default binding rule applies.

Softening Binding

did not go over this part, dense and I did not see a massive benefit.

Lexical this

normal fns abide by the 4 rules we just covered, but es6 fat arrow functions are a bit different. instead of adopting these rules, fat arrow fns adopt the this binding from the enclosing (function or global) scope.

EX:

function foo() {
	// return an arrow function
	return (a) => {
		// `this` here is lexically adopted from `foo()`
		console.log( this.a );
	};
}

var obj1 = {
	a: 2
};

var obj2 = {
	a: 3
};

var bar = foo.call( obj1 );
bar.call( obj2 ); // 2, not 3!

^ the fat arrow fn created in foo() captures whatever foo()s this is at its call-time. Since foo() was this-bound to obj1, bar will also be this-bound to obj1. The lexical binding of a fat arrow fn cant be overridden (even with new).

in my words: fat arrow fns have their this context bound to whatever this is at call-time, and cant be overridden.

The most common use of this is in callbacks: EX:

function foo() {
	setTimeout(() => {
		// `this` here is lexically adopted from `foo()`
		console.log( this.a );
	},100);
}

var obj = {
	a: 2
};

foo.call( obj ); // 2

while fat arrow fns provide an alternative to using bind() on a fn to ensure its this (which can seem attractive), its important to note that theyre essentially disabling the traditional this mechanism in favor of more widely-understood lexical scoping. Pre-ES6, we already have a faily common pattern for doing so, which is nearly indistinguishable from the spirit of ES6 fat arrow fns:

function foo() {
	var self = this; // lexical capture of `this`
	setTimeout( function(){
		console.log( self.a );
	}, 100 );
}

var obj = {
	a: 2
};

foo.call( obj ); // 2

^ note to self: you actually saw this in WFO 1.0 code!

now while both the above example and fat arrow fns seem like good solutions to not wanting to using bind(), theyre essentially fleeing from this instead of understanding/embracing it. if you see yourself writing this-style code but usually defeat the this mechanism with lexical self = this or arrow-fn tricks, you may need to:

  1. use only lexical scope and forget the false pretense of this-style code
  2. embrace this-style mechanisms completely, including using bind() where necessary.

TLDR: instead of the 4 binding rules, ES6 arrow fns use lexical scoping for this binding, meaning that it adopts the this binding (wherever it is) from its enclosing fn call.

Ch.3: `this` and object prototypes * objects are the building block upon which most of JS is built. but its a common mis-statement that 'everything in js is an object. not true. *simple primitives* (strings, numbers, booleans, null, and undefined) aren't object types.

theres a few object sub-types we can refer to as complex primitives.

  • a function is considered a 'callable object'. functions are said to be "first-class" in that they are basically just normal objects (with callable behavior boled on), so they can be handled like any other plain object.
built-in objects

theres several object sub-types, usually referred to as 'built-in objects'. for some of them, the names seem to directly correlate with their primitive types, but this isnt necessarily so:

  • String
  • Number
  • Boolean
  • Object
  • Function
  • Array
  • Date
  • RegExp
  • Error

^ peep the capitalization.

in js, these are just built-in functions. each of these can be used as a constructor (a fn cal with the new operator) with the result being a newly constructed object of the sub-type in question. ex:

var strPrimitive = "I am a string";
typeof strPrimitive;							// "string"
strPrimitive instanceof String;					// false

var strObject = new String( "I am a string" );
typeof strObject; 								// "object"
strObject instanceof String;					// true

// inspect the object sub-type
Object.prototype.toString.call( strObject );	// [object String]

also, typically in js you dont create new types (ex: a new string) like this. why is this in here? just for reference?

Contents

properties stored in an object is actually just an appearance. The engine stores values in implementation-dependent way, and may very well not store them in some object container. what is stored in the object are the property names which act as pointers/references to where those values are stored.

blah.a blah[a] <-- not standard, but [...] can hold an utf-8 string, so Super-Fun! could work here but not like blah.Super-Fun!

Computed Property Name

The myObject[...] propertyt access syntax we just described is useful if you need to use a computer expression value AS the key name like myObject[prefix + name], but thats not super helpful when declaring objects using the object-literal syntax.

ES6 adds computed property names wher eyou can do this:

var prefix = "foo";

var myObject = {
	[prefix + "bar"]: "hello",
	[prefix + "baz"]: "world"
};

myObject["foobar"]; // hello
myObject["foobaz"]; // world

^ the most common use of this is for es6 Symbols, which we will not be covering in detail in this book. Basically theyre a new primitive data type which has has 'an opaque unguessable value' (technically a string value). you'll be strongly discouraged from working with the actual value (which is different depending on js engines) so the name of Symbol will be what you use:

var myObject = {
	[Symbol.Something]: "hello world"
};

Property Vs. Method

a lot of ppl mistake functions on objects for being 'methods of an object' but technically fns never "belong" to objects, so thats a bit of semantic stretch to call it a method of an object.

everytime you access a property on an object, its a property access regardless of the type. BUT, one coudl argue a fn becomes a method not at definition time but during run-time.

TLDR: functions on objects arent reeeeally methods, but its ok if you say you.

Duplicating Objects

ES6 has defined Object.assign(..) for created shallow copies of objects. Object.assign takes a target object as its first parameter and one or more source objects as its subsequent parameters. It iterates of all enumerable keys present on the source object(s) and copies them via = assignment to the target.

EX: DO SOMETHING HERE....EXAMPLE WAS WEIRD AND COULD BE DONE BETTER.

Property Descriptors

before ES5, JS had no direct way of drawing distinction between properties, like for example, whether or not the property was read-only. in ES5, all properties are described in terms of a property descriptor:

EX:

var myObject = {
	a: 2
};

Object.getOwnPropertyDescriptor( myObject, "a" );
// {
//    value: 2,
//    writable: true,
//    enumerable: true,
//    configurable: true
// }

^ as you can see, the property descriptor for property a is much more than just the value of 2. it includes the 3 other characteristics.

we can use Object.defineProperty() to add a new property, or modify an existing one (if its configurable) with the desired characteristics. EX:

var myObject = {};

Object.defineProperty( myObject, "a", {
	value: 2,
	writable: true,
	configurable: true,
	enumerable: true
} );

myObject.a; // 2

^ so we created myObject.a in a weird way that you wouldnt want to use unless you wanted to modify one of the descriptor characteristics from its normal behavior.

Writable

boolean, states if you can change it or not:

var myObject = {};

Object.defineProperty( myObject, "a", {
	value: 2,
	writable: false, // not writable!
	configurable: true,
	enumerable: true
} );

myObject.a = 3;

myObject.a; // 2

Configurable

better to just show you...

var myObject = {
	a: 2
};

myObject.a = 3;
myObject.a;					// 3

Object.defineProperty( myObject, "a", {
	value: 4,
	writable: true,
	configurable: false,	// not configurable!
	enumerable: true
} );

myObject.a;					// 4
myObject.a = 5;
myObject.a;					// 5

Object.defineProperty( myObject, "a", {
	value: 6,
	writable: true,
	configurable: true,
	enumerable: true
} ); // TypeError

^ this is a one way action, and cannot be undone where you define the property and can only define it once.

also! configurable: false prevents the ability to use the delete operator to remove an existing property.

var myObject = {
	a: 2
};

myObject.a;				// 2
delete myObject.a;
myObject.a;				// undefined

Object.defineProperty( myObject, "a", {
	value: 2,
	writable: true,
	configurable: false,
	enumerable: true
} );

myObject.a;				// 2
delete myObject.a;
myObject.a;				// 2

^ NOW THIS IS INTERESTING!! muting the object properties so they stay static..hmm.....

Enumerable

this characteristic determines if the property will show up in object-property enumerations like for..in loops. if you set it to false, it hides it.

Immutability

sometimes you wanna make properties or objects that cant be changed. ES5 adds support for doing that in a few nuanced ways.

  1. Object constant
  • by combining writable:false and configurable:false, you can essentially create a constant that cant be changed like:
var myObject = {};

Object.defineProperty( myObject, "FAVORITE_NUMBER", {
  value: 42,
  writable: false,
  configurable: false
} );
  1. Prevent Extensions = if you wanna prevent an object from getting new properties added to it but otherwise leave the rest of the obj's properties alone, call Object.preventExtensions():
var myObject = {
	a: 2
};

Object.preventExtensions( myObject );

myObject.b = 3;
myObject.b; // undefined
  1. Seal = Object.seal() creates a 'sealed' object, which essentially takes an object, calls Object.preventExtensions() on it and also marks all properties as configurable:false, so you cant add properties or reconfigure/delete any existing ones (but you can modifiy their values).

  2. Freeze = Object.freeze() creates a frozen object, which means that it takes an object and essentially calls Object.seal() on it, but also marks all 'data accessor' properties as writable:false so that the values cant be changed.

^ this the highest level of immutability that you can attain for an objet itself, as it prevents any changes to any object or its direct properties.

[[Get]]

  • subtle but important detail on how property accesses are performed. EX:
var myObject = {
	a: 2
};

myObject.a; // 2

myObject.a is a property access, but doesnt just like in myObject for a property of a as it would seem.

According to the spec, the code above actually performs a [[Get]] operation for an object on myObject, inspecting for the property and if it finds it, it'll return it. if not, it returns undefined. This differs from variables that cant be resolved because that result wont be undefined but a ReferenceError.

let's look at this slightly deeper:

var myObject = {
	a: undefined
};

myObject.a; // undefined

myObject.b; // undefined

now from a value perspective, theres no difference between these two references..theyre both undefined. however the [[Get]] operation underneath potentially performed more work for b than a. Inspecting only the value results, you cant distinguish between whether a prop exists and holds the explicit value undefined (as above) or whether it doesnt exist and undefined was the default return value after [[Get]] failed to return something explicitly.

[[Put]]

  • as there are [[Get]] operations, theres also [[Put]], to set or create a property on the object in question, but it does (of course!) go deeper than that.

if the prop is present, the [[Put]] algorithm will check:

  1. is the property an accessor descriptor, if so call the setter if there is one.
  2. is the property a data descriptor with writable:false? if so, fail silently in non-strict mode or throw a TypeError in strict mode.
  3. otherwise, set the value to the existing property as normal.

Getters and Setters

The default [[Put]] and [[Get]] operations for objects completely control how values are set to existing or new properties, or retrieved from existing properties, respectively.

ES5 introduced a way to override part of these default operations, not on an object level but on a per-property level, through the use of getters and setters.

Getters = properties which actually call a hidden fn to retrieve a value Setters = properties which call a hidden fn to set a value

now in an object, when you defined a property to either have a getter or a setter or both, its definition becomes an "accessor descriptor", and so JS ignores the value and writable characteristics of the property (as well as configurable and enumerable).

in my words: when you define a getter or a setter at all on an object, all those other hidden properties on a property are ignored. so those 4 hidden props we talked about previously arent even considered.

EX:

var myObject = {
	// define a getter for `a`
	get a() {
		return 2;
	}
};

Object.defineProperty(
	myObject,	// target
	"b",		// property name
	{			// descriptor
		// define a getter for `b`
		get: function(){ return this.a * 2 },

		// make sure `b` shows up as an object property
		enumerable: true
	}
);

myObject.a; // 2

myObject.b; // 4

WEIRD. so in both cases here, get a(){} and defineProperty() we've created a prop on the object that doesnt hold a value but whose access automatically results in a hidden fn call to the getter fn with whatever value it returns being the result of the property access.

THAT IS SO WEIRD!!!!!!!!!

another example:

var myObject = {
	// define a getter for `a`
	get a() {
		return 2;
	}
};

myObject.a = 3;

myObject.a; // 2

^ since we've used get to return a value, the set operation will just silently throw the attempt to make it 3 away. to make this more sensible, we should define the property with a setter so a typical behavior will exist. EX:

var myObject = {
	// define a getter for `a`
	get a() {
		return this._a_;
	},

	// define a setter for `a`
	set a(val) {
		this._a_ = val * 2;
	}
};

myObject.a = 2;

myObject.a; // 4

Property Existence

how to check if an object has a property without asking for the actual property's value?

var myObject = {
	a: 2
};

("a" in myObject);				// true
("b" in myObject);				// false

myObject.hasOwnProperty( "a" );	// true
myObject.hasOwnProperty( "b" );	// false
  • in - checks to see if the property is IN the object, or if it exists at any higher level ofthe [[Prototype]] chain object traversel.
  • hasOwnProperty() - checks to see if only myObject has the prop or not, not consulting [[Prototype]] chain.

^ we'll come back to important differences between these in ch5.

Enumeration

you know a good bit of this, so I skipped alot. here's some interesting stuff here though:

var myObject = { };

Object.defineProperty(
	myObject,
	"a",
	// make `a` enumerable, as normal
	{ enumerable: true, value: 2 }
);

Object.defineProperty(
	myObject,
	"b",
	// make `b` non-enumerable
	{ enumerable: false, value: 3 }
);

myObject.propertyIsEnumerable( "a" ); // true
myObject.propertyIsEnumerable( "b" ); // false

Object.keys( myObject ); // ["a"]
Object.getOwnPropertyNames( myObject ); // ["a", "b"]
  • propertyIsEnumerable() tests whether the given name exists directly on the object and is also enumerable:true < --SUPER HANDY.

important distinction: in and hasOwnProperty differ whether or not they consult the prototype chain, but Object.keys() and Object.getOwnPropertyNames() both inspect ONLY the direct object specified.

Iteration

you know a lot of this...here's some stuff to brush up on:

  • every() and some() = new ES6 built-in iteration fns. also the return values inside these act somewhat like a break statement inside a normal for loop in that they stop iteration early before it reaches the end.

  • lots of stuff in here, skipped a bit, worth a review, but damn this is dense!!!

Review

  • many ppl claim 'everything in js is an object' but not true. objects are one or 6 primitive types. Objects have sub-types, including function and also can be behavior-specifalized like [object Array] as the internal label representing the array subtype.

  • whenever a property on an object is accessed, internally the engine invokes the [[Get]] operation for getting the value(s) and [[Set]] operation for setting the value(s), which not only looks at the object directly but will also traverse the [[Prototype]] chain if not found.

  • properties have certain characteristics that can be controleld via property dscriptors like writabe,configurable, and enumerable. Also, objects and their properties can have their mutability controlled to various levels using:

  • Object.preventExtensions()
  • Object.seal()
  • Object.freeze()
  • properties DONT HAVE TO contain values, they can 'accessor properties' as well with getters/setters. So no value will be stored, but you can invoke it and it will get/set a value.

  • you can also iterate over the values in data structures (arrays, objects, etc) using for..of syntax, which looks for either a built-in or custom @@iterator object consisting of a next() method to advance through the data values one at a time.

Ch 4: Mixing (Up) "Class" Objects

Class theory

OOP stresses that data intrinsically has associated behavior (of course, different depending on the type and nature of the data!) that operates on it, so proper to design is to package up (aka encapsulate) the data and behavior together. This is sometimes called 'Data Structures' in traditional formal computer science.

For example, you may have a string like "david". the characters are the data, but you almost never care about the data, you usually want to DO THINGS with the data, so the behaviors you can apply to that data like finding its length, searching, appending data, etc, are all designed as methods of a String class.

Any given string is just an instance of this class, which means that its a neatly collected packaging of both the character data and the functionality we can perform on it.

Classes also imply a way of classifying a certain data structure. The way do this is to thin kabout any given structure as a specific variation of a more general base definition.

Lets see an example to illustrate:

car can be described as a specific implementation of a more general class of a thing called a vehicle.

We model this relationship in software with classes by defining a Vehicle class and a Car class.

Vehicle = includes stuff like propulsion, ability to carry people, etc. as its behavior. What we define is vehicle is common to all vehicles.

Theres no reason to keep re-defining the basic essence of "ability to carry people" over and over again, so instead we define the capability once in Vehicle and then define Car to INHERIT (or EXTEND) the base definition from Vehicle. Car is said to specialize the general Vehicle definition.

While Vehicle and Car collectively define the behavior by way of methods, the data in an instance would be things like the unique VIN, etc.

another concept: POLYMORPHISM = the idea that a general behavior from a parent class can be overridden in a child class to give it more specifics.

Class theory strongly suggests that a parent class and a child class share the same method name for a certain behavior, so the child overrides the parent. Doing so in JS howeer, is opting into frustration and code brittleness.

Class design patterns

You may never thought of classes as a 'design pattern', and its a common assumption that OO classes are the lower level mechanics by which we implement all (higher level) design patterns (like "iterator", "Factory" , "Observer", etc) as if OO is a GIVEN foundation for all proper code.

you may have been taught that classes are the proper way to organize spaghetti procedural code into something well-organized. if you've seen "functional programming" you'll know this is just one of several design patterns. Some languages (like Java) dont give you the choice; everything's a class. other languages give you both procedural and class-oriented syntaxes so its left to the dev's choice which style or mixture to use

JS "classes"

does js actually have classes? NOPE.

you can do class-ish things in JS and JS tries to satisfy the super pervasive desire to design with classes by providing a seemingly class-like syntax.

The syntax might look like classs but its as if JS mechanics are fighting against you using the class design pattern, b/c behind the curtain, the mechanisms you build on are operating quite differently.

Summary: classes are optional in software design, and you can choose if you wanna use em in JS.

Class Mechanics

class = blueprint instance of a class = an actual building from the blueprint

you can examine a blueprint to understand how the building was structured, but if you wanna open the door, you have to go to the building itself. A class is a blueprint, to actually get something you can interact with, you must build (aka 'instantiate) something from the class. The end result of such 'construction' is an object, typically called an 'instance', which we can directly call methods on and access any public data properties from, as necessary.

The object instance IS A COPY of all the characteristics defined by the class.

constructor

Instances of classes are created by a special method of the class, called a constructor. Its job is to initialize any info (state) the instance will need. EX:

class CoolGuy {
	specialTrick = nothing

	CoolGuy( trick ) {
		specialTrick = trick
	}

	showOff() {
		output( "Here's my trick: ", specialTrick )
	}
}

To make a CoolGuy instance, we would call the class constructor:

Joe = new CoolGuy( "jumping rope" )

Joe.showOff() // Here's my trick: jumping rope

^ CoolGuy class has a constructor called CoolGuy() which is actually what we call when we say new CoolGuy(). we get an instance of our class (an object) back from the constructor and we can call the method showOff() which prints out that particular coolguy's special trick.

TO KNOW: the constructor of a class belongs to the class, almost universally with the SAME NAME (as above) of the class itself. Also, constructors pretty much always need to be called with the new operator to let the language engine know you want to construct a new class instance.

Class Inheritance

In class-oriented languges, not only can you define a class which can be instantiated itself, but you can also define another classes that inherits from the first class.

This is considered a 'child class', where the first class is the 'parent class'. child inherits parent's characteristics, but can be its own person, so it can override characteristics.

Polymorphism

Imagine 'car' and 'vehicle' classes again. Car defines its own drive() method overriding the method of the same name from Vehicle. but inside Car's drive() method is inherited:drive(), which indicates that Car can still reference the orginial method its overriding. This technique is called "polymorphism".

polymorphism: any method can reference another method at a higher level of the inheritance hierarchy (aka: 'look one level up').

In many languages super keyword is used, in place of this examples inherited: keyword

Take a look at this example:

class Vehicle {
	engines = 1

	ignition() {
		output( "Turning on my engine." )
	}

	drive() {
		ignition()
		output( "Steering and moving forward!" )
	}
}

class Car inherits Vehicle {
	wheels = 4

	drive() {
		inherited:drive()
		output( "Rolling on all ", wheels, " wheels!" )
	}
}

class SpeedBoat inherits Vehicle {
	engines = 2

	ignition() {
		output( "Turning on my ", engines, " engines." )
	}

	pilot() {
		inherited:drive()
		output( "Speeding through the water with ease!" )
	}
}

strong idea behind polymorphism = any mthod can reference another method at a higher level of the inheritance hierarchy.

in many languages, the super keyword is used in place of inherited:, which leans on the idea that a 'super class' is the parent/ancestor of the current class.

another aspect of polymorphism is that methods of the same name can have multiple definitions at different levels of inheritance hierarchy. if you look at the above example, you'll see that drive() is defined differently in both Car and Speedboat classes.

one last thing: an interesting implication of polymorphism is that a method will change depending on which which class you are referencing an instance of. now this sounds weird right? Lets break this down...

look at the Speedboat class which inherits the Vehicle class. Speedboat inherits the drive() method from Vehicle and as we see in Vechicle':

class Vehicle {
	engines = 1

	ignition() {
		output( "Turning on my engine." )
	}

	drive() {
		ignition()
		output( "Steering and moving forward!" )
	}
}

so Speedboat inherits Vehicle's drive() method, which calls ignition(), but as we see in Speedboat, it has its own declaration of ignition() so which gets called?

class SpeedBoat inherits Vehicle {
	engines = 2

	ignition() {
		output( "Turning on my ", engines, " engines." )
	}

	pilot() {
		inherited:drive()
		output( "Speeding through the water with ease!" )
	}
}

Speedboat's declaration of ignition() wins out as it is closer in the hierarchy to Speedboat. This may seem trivial and academic-speak, but understanding these details is necessary to properly contrast similar behaviors in JS [[prototype]] mechanism.

Super = a way for inherited classes to relatively reference the class inherited from.

  • remember: dont let polymorphism confuse you into thinking a child classe is linked to its parent class. a child class instead gets a copy of what it needs from the parent class. Class inheritance implies copies.

Mixins

JS' object mechanism doesnt automatically perform copy behavior when you 'inherit' or 'instantiate'. THERE ARE NO CLASSES in JS to instantiate, only objects. and objects dont get copied to other objects, they get linked together (more on that in the next chapter).

Since observed class behaviors in other languages implie copies, lets look at how JS devs FAKE the missing copy behavior of classes in JS: MIXINS. we'll look at 2 types: explicit and implicit.

Explicit Mixins

looking at out Vechicle and Car examples from before...JS wont automatically copy behavior from Vehicle to Car, but we can create a utility that manually copies:

// vastly simplified `mixin(..)` example:
function mixin( sourceObj, targetObj ) {
	for (var key in sourceObj) {
		// only copy if not already present
		if (!(key in targetObj)) {
			targetObj[key] = sourceObj[key];
		}
	}

	return targetObj;
}

var Vehicle = {
	engines: 1,

	ignition: function() {
		console.log( "Turning on my engine." );
	},

	drive: function() {
		this.ignition();
		console.log( "Steering and moving forward!" );
	}
};

var Car = mixin( Vehicle, {
	wheels: 4,

	drive: function() {
		Vehicle.drive.call( this );
		console.log( "Rolling on all " + this.wheels + " wheels!" );
	}
} );

subtle, but important: as you see, we're not dealing with classes anymore, but objects, since theyre are no classes in JS.

The name "mixin" comes from an alternate way of explaining the task: Car has Vehicles contents mixed-in, just like you mix in chocolate chips into your favorite cookie dough.

As a result of the copy operation, Car will operate somewhat separately from Vehicle. If you add a property onto Car, it will not affect Vehicle, and vice versa.

JS fns cant really be duplicated, so what you actually end up with is a duplicated reference to the same shared fn object. so if you modified one of the shared fn objects (like ignition()) you'd be affecting both Vehicle and Car.

ITS A LOT. I STOPPED FROM 367-374

Parasitic Inheritance

a variation on this explicit mixin pattern is called 'parasitic inheritance':

// "Traditional JS Class" `Vehicle`
function Vehicle() {
	this.engines = 1;
}
Vehicle.prototype.ignition = function() {
	console.log( "Turning on my engine." );
};
Vehicle.prototype.drive = function() {
	this.ignition();
	console.log( "Steering and moving forward!" );
};

// "Parasitic Class" `Car`
function Car() {
	// first, `car` is a `Vehicle`
	var car = new Vehicle();

	// now, let's modify our `car` to specialize it
	car.wheels = 4;

	// save a privileged reference to `Vehicle::drive()`
	var vehDrive = car.drive;

	// override `Vehicle::drive()`
	car.drive = function() {
		vehDrive.call( this );
		console.log( "Rolling on all " + this.wheels + " wheels!" );
	};

	return car;
}

var myCar = new Car();

myCar.drive();
// Turning on my engine.
// Steering and moving forward!
// Rolling on all 4 wheels!

Review

Polymorphism = having different fns at multiple levels of an inheritance chain with the same name

Polymorphism may seem like it implies a referential relative link from child back to parent, but its still just a result of copy behavior.

JS does not automatically create copies (as classes imply) between objects.

The mixin pattern (explicit and implicit) is often used to sort of emulate class copy behavior, but this usually results in ugly and brittle syntax, leading to hard to understand/maintain the code.

In general, faking classes in JS often sets more landmines for future coding than solving present real problems.

Ch.5 Prototypes

Objects in JS have an internal property called [[prototype]] which is simply a reference to another object. EX:

var myObject = {
	a: 2
};

myObject.a; // 2

what is the [[prototype]] reference used for? In ch3 we examined the [[Get]] operation that is invoked when you reference a property on an object, such as myObject.a. For that default [[Get]] operation, the first step is to check if the object itself has the property a on it and if so, its used.

But what happens if a isnt present on myObject that brings our attention now to the [[Prototype]] link of the object.

'The default [[Get]] operation proceeds to follow the [[Prototype]] link of the object if it cannot find the requested property on the object directly.'

^ no clue what that means!!!!!

var anotherObject = {
	a: 2
};

// create an object linked to `anotherObject`
var myObject = Object.create( anotherObject );

myObject.a; // 2

^ Here we have myObject which is [[prototype]] linked to anotherObject. clearly myObject.a doesnt exist but nevertheless, the property access succeeds (being found on anotherObject instead) and finds the value 2.

but if a wasnt found on anotherObject either? its [[prototype]] chain is consulted.

this process continues until a matching property name is found or the [[prototype]] chain ends. if its never found, the [[Get]] operation returns undefined.

Object.prototype

exactly where does the [[prototype]] chain 'end'?

[[prototype]] chain is the built in Object.prototype. This object includes a variety of common utilities used all over JS, because all normal (built-in) objects in JS 'descend from' (aka have at the top of their [[prototype]] chain) the Object.prototype object.

some utilities found in Object.prototype include .toString() and valueOf() and .hasOwnProperty()...

Setting and Shadowing Properties

take a look here:

myObject.foo = "bar";

if the myObject object already has a property called foo directly on it, the assignment is as simple as changing the value of the existing property. if not, the [[prototype]] chain is traversed. if foo isnt found on the chain, foo property is added to myObject with the specified value, as expected.

However if foo is alrady present on the prototype chain, some surprising behavior can occur with myObject.foo = "bar"; assignment...more on that in a moment.

if the proporty name foo is on both myObject and its [[prototype]] chain, this is called shadowing. the foo property directly on myObject shadows any foo property which appears higher in the chain, bc myObject.foo would always find the foo property thats lowest in the chain.

THIS IS DENSE!!!!!

lets examine 3 scenarios when myObject.foo isnt found on the object directly but is at a higher level of myObject's prototype chain:

  1. if the prop foo is found on the chain and its not marked as read-only, a new property called foo is added directly to myObject, resulting in a shadowed property.

  2. if foo is found but marked as read-only, then both the setting of that existing property and the creation of the shadowed property on myObject are disallowed. NO SHADOWING WILL OCCUR.

  3. if foo is found on the chain and its a setter, then the setter will always be called. no foo will be added to (aka shadowed on) myObject, nor will the foo setter be redefined.

SHADOWING = creating a new property on an object based on a property that already exists in the object's [[prototype]] chain.

ended at line 103.