links: JS MOC,
Values vs References
In chapter 2, we introduced two main types of values: primitives and objects. But we didn’t discuss yet one key difference between the two: how these values are assigned and passed around.
In many languages, the developer can choose between assigning/passing a value as the value itself, or as a reference to the value. In JS, however, this decision is entirely determined by the kind of the value. That surprises a lot of developers when they start using JS.
If you assign/pass a value itself, the value is copied. For example:
var myName = 'Subramanya'
var yourName = myName
Here, the yourName variable has a separate copy of the “Subramanya” string from the value that’s stored in myName. That’s because the value is a primitive, and primitive values are always assigned/passed as value copies
Here’s how you can prove there’s two separate values involved:
var myName = 'Subramanya'
var yourName = myName
myName = 'Kyle'
console.log(myName)
// kyle
console.log(yourName)
// Subramanya
See how yourName wasn’t affected by the re-assignment of myName to 'Kyle'? That’s because each variable holds it’s own copy of the value.
By contrast, references are the idea that two or more variables are pointing at the same value, such that modifying this shared value would be reflected by an access via any of those references. In JS only object values(arrays, objects, functions, etc) are treated as references.
Consider:
var myAddress = {
street: 'Main Road',
city: 'Kadapa',
state: 'Andhra Pradesh'
}
var yourAddress = myAddress
myAddress.street = 'Gandhi Road'
console.log(yourAddress.street)
// Gandhi Road
Because the value assigned to myAddress is an object, it’s held/assigned by reference, and thus the statement to the yourAddress variable is a copy of the reference, not the object value itself. That’s why the updated value assigned to the myAddress.street is reflected when we access yourAddress.street. myAddress and yourAddress have copies of the reference to the single shared object, so an update to one is an update to both.
Again JS chooses value-copy vs reference-copy behavior based on the value-type. Primitives are held by values, Objects are held by reference. There’s no way to override this in JS, in either direction.
So Many Function Forms
Consider this snippet:
var awesomeFunction = function(coolThings) {
// ..
return amazingStuff
}
The function expression here is referred as anonymous function expression, since it has no name identifier between the function keyword and the (..) parameter list. This point confuses many JS developers because as of ES6, JS performs a “name inference” on an anonymous function:
awesomeFunction.name
// "awesomeFunction"
The name property of a function will reveal either it’s directly given name (in the case of declaration) or its inferred name in the case of an anonymous expression. The value is generally used by developer tools when inspecting a function value or when reporting an error stack trace.
So even an anonymous function expression might get a name. However, name inference only happens in limited cases such as when the function expression is assigned (with “ // let awesomeFunction = .. // const awesomeFunction = .. var awesomeFunction = function someName(coolThings) { // .. return amazingStuff }
awesomeFunction.name // someName
The function expression is a *named function expression*, since the identifier `someName` is directly associated with the function expression at compile time; the association with the identifier `awesomeFunction` still doesn't happen until runtime at the time of that statement. Those two identifiers doesn't have to match; sometimes it makes sense to have them be different, other times it's better to have them be the same.
Notice also that the explicit function name, the identifier `someName`, takes precedence when assigning a name for the `name` property.
If a function exists in your program, It has a purpose; otherwise take it out! If it has a purpose it has a natural name that describes that purpose. So In order to avoid extra mental work of reading function body it's advised to name a function.
There are many other function form definitions in JS. Here are some examples:
// generator function declaration function *two() {..}
// async function declaration async function three() {..}
// async function generator declaration async function *four() {..}
// named function export declaration (ES6) export function five() {..}
Here are some examples of expression form:
// IIFE (function () { .. })() (function namedIIFE() { .. })()
// asynchronous IIFE (async function () { .. })() (async function namedIIFE() { .. })()
// arrow function expressions var f f = () ⇒ 42 f = x ⇒ x * 2 f = (x) ⇒ x * 2 f = (x, y) ⇒ x * y f = x ⇒ ({x: x * 2}) f = x ⇒ { return x * 2 } f = async x ⇒ { var y = await doSomethingAsync(x) return y * 2 } someOperation(x ⇒ x * 2) // ..
Keep in mind that arrow functions are **syntactically anonymous**, meaning the syntax doesn't provide a way to provide a direct name identifier for the function. The function expression may get an inferred name, but only if it's one of the assignment forms, not in the form of being passed as a function call argument (as in the last line of snipped ⬆️)
Use the appropriate function form (arrow function or normal function) which is suitable for the job. Don't blindly bias to arrow or function form everywhere.
Functions can also be specified in class definitions and object literal definitions. they are typically referred to as "methods" when in these forms, though in JS this term doesn't have much observable difference over "function"
class SomethingKindaGreat { // class methods coolMethod() { .. } // no commas! boringMethod() { .. } }
var EntirelyDifferent = { // object methods coolMethod() { .. }, // commas! boringMethod() { .. },
// anonymous function expression property
oldSchool: function () { .. }
}
There's no simple shortcut here; you just have to build familiarity with all the function forms so you can recognize them in existing code and use them appropriately in the code you write. Study them closely and practice!.
### Coercive Conditional Comparison
This section is about conditional expressions needing to perform coercion-oriented comparisons to make their decisions.
`if` and`? :` ternary statements as well as the `for` and `while` loops, all perform an implicit value comparison. But what sort? Is it "strict" or "coercive"? Both actually
Consider:
```js
var x = 1
if(x) {
// will run
}
while(x) {
// will run, once
x = false
}
You might think of these (x) conditional expressions like this:
var x = 1
if(x) {
// will run
}
while(x == true) {
// will run once
x = false
}In this specific case — the value of x being 1 — that mental model works, but it’s not accurate more broadly. Consider:
var x = "hello"
if(x) {
// will run
}
if(x == true) {
// won't run :(
}Oops. what is the if statement actually doing? This is the more accurate mental model.
var x = "hello"
if(Boolean(x) == true) {
// will run
}
// which is the same as
if(Boolean(x) === true) {
// will run
}Since the Boolean(..) function always returns a value of type boolean, the or in this snippet is irrelevant; they’ll both do the same thing. But the important part is to see that before comparison, a coercion occurs, from whatever type x currently is to boolean
You can’t just get away from coercions in JS comparisons. Buckle down and learn them.
Prototypal classes
Another way of wiring up prototype linkages served as the (honestly ugly) predecessor to the elegance of ES6 class system, and is referred to as prototypal classes.
| Tip: |
|---|
| While this style of coding is quite uncommon in JS these days, it’s still perplexingly rather common to be asked in an interview |
Let’s first recall the Object.create(..) style of coding:
var Classroom = {
welcome() {
console.log('welcome students 🙏 ')
}
}
var mathClass = Object.create(Classroom)
mathClass.welcome()
// 'welcome students 🙏 'Here a mathClass object is linked via it’s prototype to a Classroom object. Through this linkage the function call mathClass.welcome() is delegated to the method defined in Classroom.
The prototypal class pattern would have labeled this delegation behavior “inheritance”, and alternatively have defined it (with the same behavior) as:
function Classroom() {
// ..
}
Classroom.prototype.welcome = function hello() {
console.log('welcome students 🙏 ')
}
var mathClass = new Classroom()
mathClass.welcome()
// welcome students 🙏 All functions by default reference an empty object at a property named prototype. Despite the confusing name, this is not the functions’s prototype (where the function is prototype linked to), but rather the prototype object to link to when other objects are created by calling the function with new.
We add a welcome property on that empty object (Classroom.prototype), pointing at the hello function.
Then new Classroom() creates a new object (assigned to mathClass), and prototype links it to the Classroom.prototype object.
Though mathClass does not have a welcome() property/function, it successfully delegated to Classroom.prototype.welcome()
This “prototypal class” pattern is now strongly discouraged, in favor of ES6’ s class mechanism:
class Classroom() {
constructor() {
// ..
}
welcome() {
console.log('welcome students 🙏 ')
}
}
var mathClass = new Classroom()
mathClass.welcome()
// welcome students 🙏 Under the covers, the same prototype linkage is wired up, but this class syntax fits the class-oriented design pattern much more cleanly than “prototypal classes”
tags: javascript , fundamentals