Renan Roggia
I consider myself a tech problem solver.
this
Or That?However, the
this
mechanism provides a more elegant way of implicitly "passing along" an object reference, leading to cleaner API design and easier re-use.
To be clear,
this
does not, in any way, refer to a function's lexical scope.
You cannot use a
this
reference to look something up in a lexical scope. It is not possible.
It is contextual based on the conditions of the function's invocation.
this
binding has nothing to do with where a function is declared, but has instead everything to do with the manner in which the function is called.
When a function is invoked, an activation record, otherwise known as an execution context, is created. This record contains information about where the function was called from (the call-stack), how the function was invoked, what parameters were passed, etc. One of the properties of this record is the
this
reference which will be used for the duration of that function's execution.
this
All Makes Sense Now!the call-site: the location in code where a function is called (not where it's declared).
The first rule we will examine comes from the most common case of function calls: standalone function invocation.
even though the overall
this
binding rules are entirely based on the call-site, the global object is only eligible for the default binding if the contents offoo()
are not running instrict mode
Regardless of whether
foo()
is initially declared onobj
, or is added as a reference later (as this snippet shows), in neither case is the function really "owned" or "contained" by theobj
object.
However, the call-site uses the
obj
context to reference the function, so you could say that theobj
object "owns" or "contains" the function reference at the time the function is called.
When there is a context object for a function reference, the implicit binding rule says that it's that object which should be used for the function call's
this
binding.
We create a function
bar()
which, internally, manually callsfoo.call(obj)
, thereby forcibly invokingfoo
withobj
binding forthis
. No matter how you later invoke the functionbar
, it will always manually invokefoo
withobj
. This binding is both explicit and strong, so we call it hard binding.
In JS, constructors are just functions that happen to be called with the
new
operator in front of them. They are not attached to classes, nor are they instantiating a class. They are not even special types of functions. They're just regular functions that are, in essence, hijacked by the use ofnew
in their invocation.
there's really no such thing as "constructor functions", but rather construction calls of functions.
When a function is invoked with
new
in front of it, otherwise known as a constructor call, the following things are done automatically:
- a brand new object is created (aka, constructed) out of thin air
- the newly constructed object is
[[Prototype]]
-linked- the newly constructed object is set as the
this
binding for that function call- unless the function returns its own alternate object, the
new
-invoked function call will automatically return the newly constructed object.
It should be clear that the default binding is the lowest priority rule of the 4.
So, explicit binding takes precedence over implicit binding, which means you should ask first if explicit binding applies before checking for implicit binding.
new binding is more precedent than implicit binding.
One of the capabilities of
bind(..)
is that any arguments passed after the firstthis
binding argument are defaulted as standard arguments to the underlying function (technically called "partial application", which is a subset of "currying").
If you pass
null
orundefined
as athis
binding parameter tocall
,apply
, orbind
, those values are effectively ignored, and instead the default binding rule applies to the invocation.
Instead of using the four standard
this
rules, arrow-functions adopt thethis
binding from the enclosing (function or global) scope.
There are four types of rules for determining this
. They follow these rules:
new
binding: this
is the newly created object. Using new
keyword. var bar = new foo()
this
is the specified object during binding. Using call
or apply
, for instance, var bar = foo.call(obj2)
this
is the object that contains the function reference at the time the function is called. With context object, for instance, obj.foo()
.this
is undefined or the global
object. Standalone function invocation, for instance, foo()
.Objects come in two forms: the declarative (literal) form, and the constructed form.
The only difference really is that you can add one or more key/value pairs to the literal declaration, whereas with constructed-form objects, you must add the properties one-by-one.
Objects are the general building block upon which much of JS is built. They are one of the 6 primary types (called "language types" in the specification) in JS:
string
number
boolean
null
undefined
object
Note that the simple primitives (
string
,number
,boolean
,null
, andundefined
) are not themselvesobjects
.
function
is a sub-type of object (technically, a "callable object"). Functions in JS are said to be "first class" in that they are basically just normal objects (with callable behavior semantics bolted on), and so they can be handled like any other plain object.
Arrays are also a form of objects, with extra behavior. The organization of contents in arrays is slightly more structured than for general objects.
There are several other object sub-types, usually referred to as built-in objects. For some of them, their names seem to imply they are directly related to their simple primitives counter-parts, but in fact, their relationship is more complicated, which we'll explore shortly.
String
Number
Boolean
Object
Function
Array
Date
RegExp
Error
Each of these built-in functions can be used as a constructor (that is, a function call with the
new
operator -- see Chapter 2), with the result being a newly constructed object of the sub-type in question.
the language automatically coerces a
"string"
primitive to aString
object when necessary, which means you almost never need to explicitly create the Object form
The
.a
syntax is usually referred to as "property" access, whereas the["a"]
syntax is usually referred to as "key" access.
One subset solution is that objects which are JSON-safe (that is, can be serialized to a JSON string and then re-parsed to an object with the same structure and values) can easily be duplicated with:
This approach is the highest level of immutability that you can attain for an object itself, as it prevents any changes to the object or to any of its direct properties (though, as mentioned above, the contents of any referenced other objects are unaffected).
The default built-in
[[Get]]
operation for an object first inspects the object for a property of the requested name, and if it finds it, it will return the value accordingly.
When invoking
[[Put]]
, how it behaves differs based on a number of factors, including (most impactfully) whether the property is already present on the object or not.
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.
Getters are properties which actually call a hidden function to retrieve a value. Setters are properties which actually call a hidden function to set a value.
When you define a property to have either a getter or a setter or both, its definition becomes an "accessor descriptor" (as opposed to a "data descriptor").
You'll notice that
myObject.b
in fact exists and has an accessible value, but it doesn't show up in afor..in
loop (though, surprisingly, it is revealed by thein
operator existence check). That's because "enumerable" basically means "will be included if the object's properties are iterated through".
Whereas
in
vs.hasOwnProperty(..)
differ in whether they consult the[[Prototype]]
chain or not,Object.keys(..)
andObject.getOwnPropertyNames(..)
both inspect only the direct object specified.
The
for..of
loop asks for an iterator object (from a default internal function known as@@iterator
in spec-speak) of the thing to be iterated, and the loop then iterates over the successive return values from calling that iterator object'snext()
method, once for each loop iteration.
@@iterator
is not the iterator object itself, but a function that returns the iterator object -- a subtle but important detail!
"Class/Inheritance" describes a certain form of code organization and architecture -- a way of modeling real world problem domains in our software.
Another key concept with classes is "polymorphism", which describes the idea that a general behavior from a parent class can be overridden in a child class to give it more specifics. In fact, relative polymorphism lets us reference the base behavior from the overridden behavior.
Syntactic sugar and (extremely widely used) JS "Class" libraries go a long way toward hiding this reality from you, but sooner or later you will face the fact that the classes you have in other languages are not like the "classes" you're faking in JS.
You must instantiate the
Stack
class before you have a concrete data structure thing to operate against.
This technique is called "polymorphism", or "virtual polymorphism". More specifically to our current point, we'll call it "relative polymorphism".
Polymorphism is a much broader topic than we will exhaust here, but our current "relative" semantics refers to one particular aspect: the idea that any method can reference another method (of the same or different name) at a higher level of the inheritance hierarchy. We say "relative" because we don't absolutely define which inheritance level (aka, class) we want to access, but rather relatively reference it by essentially saying "look one level up".
Note: Another thing that traditional class-oriented languages give you via
super
is a direct way for the constructor of a child class to reference the constructor of its parent class. This is largely true because with real classes, the constructor belongs to the class. However, in JS, it's the reverse -- it's actually more appropriate to think of the "class" belonging to the constructor (theFoo.prototype...
type references). Since in JS the relationship between child and parent exists only between the two.prototype
objects of the respective constructors, the constructors themselves are not directly related, and thus there's no simple way to relatively reference one from the other (see Appendix A for ES6class
which "solves" this withsuper
).
When classes are inherited, there is a way for the classes themselves (not the object instances created from them!) to relatively reference the class inherited from, and this relative reference is usually called
super
.
Let's examine this statement:
Vehicle.drive.call( this )
. This is what I call "explicit pseudo-polymorphism". Recall in our previous pseudo-code this line wasinherited:drive()
, which we called "relative polymorphism".
Since the two objects also share references to their common functions, that means that even manual copying of functions (aka, mixins) from one object to another doesn't *actually emulate* the real duplication from class to instance that occurs in class-oriented languages.