Skip to content

Commit 5b32c45

Browse files
committed
types-grammar: filling out content in chapter 3, as well as adding related clarifications in chapters 1 and 2
1 parent 5f36f4a commit 5b32c45

File tree

5 files changed

+264
-19
lines changed

5 files changed

+264
-19
lines changed

types-grammar/README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88

99
* [Foreword](foreword.md) (by TBA)
1010
* [Preface](../preface.md)
11-
* [Chapter 1: Primitives](ch1.md)
12-
* [Chapter 2: Value Behaviors](ch2.md)
11+
* [Chapter 1: Primitive Values](ch1.md)
12+
* [Chapter 2: Primitive Behaviors](ch2.md)
1313
* [Chapter 3: Object Values](ch3.md)
1414
* [Chapter 4: Coercing Values](ch4.md)
1515
* Chapter 5: TODO

types-grammar/ch1.md

+63-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# You Don't Know JS Yet: Types & Grammar - 2nd Edition
2-
# Chapter 1: Primitives
2+
# Chapter 1: Primitive Values
33

44
| NOTE: |
55
| :--- |
@@ -13,17 +13,17 @@ Here, we'll look at the core value types of JS, specifically the non-object type
1313

1414
JS doesn't apply types to variables or properties -- what I call, "container types" -- but rather, values themselves have types -- what I call, "value types".
1515

16-
The language provides seven built-in, primitive (non-object) value types:
16+
The language provides seven built-in, primitive (non-object) value types: [^PrimitiveValues]
1717

18-
* `null`
1918
* `undefined`
19+
* `null`
2020
* `boolean`
21-
* `string`
2221
* `number`
2322
* `bigint`
2423
* `symbol`
24+
* `string`
2525

26-
These value-types define collections of one or more concrete values, each with a set of shared behaviors for all values of that type.
26+
These value-types define collections of one or more concrete values, each with a set of shared behaviors for all values of each type.
2727

2828
### Type-Of
2929

@@ -48,6 +48,48 @@ typeof greeting; // "string"
4848

4949
JS variables themselves don't have types. They hold any arbitrary value, which itself has a value-type.
5050

51+
### Non-objects?
52+
53+
What specifically makes the 7 primitive value types distinct from the object value types (and sub-types)? Why shouldn't we just consider them all as essentially *objects* under the covers?
54+
55+
Consider:
56+
57+
```js
58+
myName = "Kyle";
59+
60+
myName.nickname = "getify";
61+
62+
console.log(myName.nickname); // undefined
63+
```
64+
65+
This snippet appears to silently fail to add a `nickname` property to a primitive string. Taken at face value, that might imply that primitives are really just objects under the covers, as many have (wrongly) asserted over the years.
66+
67+
| WARNING: |
68+
| :--- |
69+
| One might explain that silent failure as an example of *auto-boxing* (see "Automatic Objects" in Chapter 3), where the primitive is implicitly converted to a `String` instance wrapper object while attempting to assign the property, and then this internal object is thrown away after the statement completes. In fact, I said exactly that in the first edition of this book. But I was wrong; oops! |
70+
71+
Something deeper is at play, as we see in this version of the previous snippet:
72+
73+
```js
74+
"use strict";
75+
76+
myName = "Kyle";
77+
78+
myName.nickname = "getify";
79+
// TypeError: Cannot create property 'nickname'
80+
// on string 'Kyle'
81+
```
82+
83+
Interesting! In strict-mode, JS enforces a restriction that disallows setting a new property on a primitive value, as if implicitly promoting it to a new object.
84+
85+
By contrast, in non-strict mode, JS allows the violation to go unmentioned. So why? Because strict-mode was added to the language in ES5.1 (2011), more than 15 years in, and such a change would have broken existing programs had it not been defined as sensitive to the new strict-mode declaration.
86+
87+
So what can we conclude about the distinction between primitives and objects? Primitives are values that *are not allowed to have properties*; only objects are allowed such.
88+
89+
| TIP: |
90+
| :--- |
91+
| This particular distinction seems to be contradicted by expressions like `"hello".length`; even in strict-mode, it returns the expected value `5`. So it certainly *seems* like the string has a `length` property! But, as just previously mentioned, the correct explanation is *auto-boxing*; we'll cover the topic in "Automatic Objects" in Chapter 3. |
92+
5193
## Empty Values
5294

5395
The `null` and `undefined` types both typically represent an emptiness or absence of value.
@@ -1108,12 +1150,28 @@ If the registry doesn't have a symbol under that specified *key*, a new symbol (
11081150
11091151
Going in the opposite direction, if you have the symbol value itself, and want to retrieve the *key* it's registered under, `Symbol.keyFor(..)` takes the symbol itself as input, and returns the *key* (if any). That's useful in case it's more convenient to pass around the *key* string value than the symbol itself.
11101152
1153+
### Object or Primitive?
1154+
1155+
Unlike other primitives like `42`, where you can create multiple copies of the same value, symbols *do* act more like specific object references in that they're always completely unique (for purposes of value assignment and equality comparison). The specification also categorizes the `Symbol()` function under the "Fundamental Objects" section, calling the function a "constructor", and even defining its `prototype` property.
1156+
1157+
However, as mentioned earlier, `new` cannot be used with `Symbol(..)`; this is similar to the `BigInt()` "constructor". We clearly know `bigint` values are primitives, so `symbol` values seem to be of the same *kind*.
1158+
1159+
And in the specification's "Terms and Definitions", it lists symbol as a primitive value. [^PrimitiveValues] Moreover, the values themselves are used in JS programs as primitives rather than objects. For example, symbols are primarily used as keys in objects -- we know objects cannot use other object values as keys! -- along with strings, which are also primitives.
1160+
1161+
As mentioned earlier, some JS engines even internally implement symbols as unique, monotonically incrementing integers (primitives!).
1162+
1163+
Finally, as explained at the top of this chapter, we know primitive values are *not allowed* to have properties set on them, but are *auto-boxed* (see "Automatic Objects" in Chapter 3) internally to the corresponding object-wrapper type to facilitate property/method access. Symbols follow all these exact behaviors, the same as all the other primitives.
1164+
1165+
All this considered, I think symbols are *much more* like primitives than objects, so that's how I present them in this book.
1166+
11111167
## Primitives Are Built-In Types
11121168
11131169
We've now dug deeply into the seven primitive (non-object) value types that JS provides automatically built-in.
11141170
11151171
Before we move on to discussing JS's built-in object value type, we want to take a closer look at the kinds of behaviors we can expect from JS values. We'll do so in-depth, in the next chapter.
11161172
1173+
[^PrimitiveValues]: "4.4.5 primitive value", ECMAScript 2022 Language Specification; https://tc39.es/ecma262/#sec-primitive-value ; Accessed August 2022
1174+
11171175
[^UTFUCS]: "JavaScript’s internal character encoding: UCS-2 or UTF-16?"; Mathias Bynens; January 20 2012; https://mathiasbynens.be/notes/javascript-encoding ; Accessed July 2022
11181176
11191177
[^IEEE754]: "IEEE-754"; https://en.wikipedia.org/wiki/IEEE_754 ; Accessed July 2022

types-grammar/ch2.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# You Don't Know JS Yet: Types & Grammar - 2nd Edition
2-
# Chapter 2: Value Behaviors
2+
# Chapter 2: Primitive Behaviors
33

44
| NOTE: |
55
| :--- |
@@ -98,7 +98,7 @@ Additionally, most of the primitive value-types define their own methods with sp
9898

9999
| NOTE: |
100100
| :--- |
101-
| Technically, these sorts of property/method accesses on primitive values are facilitated by an implicit coercive behavior called *auto-boxing*, which we'll cover in the next chapter. |
101+
| As already briefly mentioned in Chapter 1, technically, these sorts of property/method accesses on primitive values are facilitated by an implicit coercive behavior called *auto-boxing*. We'll cover this in detail in "Automatic Objects" in Chapter 3. |
102102

103103
## Primitive Assignments
104104

types-grammar/ch3.md

+188-7
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,207 @@
77

88
Now that we're comfortable with the built-in primitive types, we turn our attention to the `object` types in JS.
99

10-
The "Objects & Classes" title of this series covers objects in-depth already, so make sure you've read that before continuning with this chapter. Here we'll focus our attention on how the `object` value-type behaves and interacts with other values in JS.
10+
I could write a whole book talking about objects in-depth; in fact, I already did! The "Objects & Classes" title of this series covers objects in-depth already, so make sure you've read that before continuning with this chapter.
1111

12-
## Nature Of Objects
12+
Rather than repeat that book's content, here we'll focus our attention on how the `object` value-type behaves and interacts with other values in JS.
1313

14-
The `object` value-type comprises several sub-types with specialized behaviors. These include:
14+
## Types of Objects
1515

16-
* general objects
16+
The `object` value-type comprises several sub-types, each with specialized behaviors, including:
17+
18+
* plain objects
1719
* fundamental objects (boxed primitives)
20+
* built-in objects
1821
* arrays
1922
* regular expressions
2023
* functions (aka, "callable objects")
2124

22-
But one shared characteristic is that all objects are capable of acting as collections of values.
25+
Beyond the specialized behaviors, one shared characteristic is that all objects can act as collections (of properties) holding values (including functions/methods).
2326

24-
// TODO
27+
## Plain Objects
28+
29+
The general object value-type is sometimes referred to as *plain ol' javascript objects* (POJOs).
30+
31+
Plain objects have a literal form:
32+
33+
```js
34+
address = {
35+
street: "12345 Market St",
36+
city: "San Francisco",
37+
state: "CA",
38+
zip: "94114"
39+
};
40+
```
41+
42+
This plain object (POJO), as defined with the `{ .. }` curly braces, is a collection of named properties (`street`, `city`, `state`, and `zip`). Properties can hold any values, primitives or other objects (including arrays, functions, etc).
43+
44+
The same object could also have been defined imperatively using the `new Object()` constructor:
45+
46+
```js
47+
address = new Object();
48+
address.street = "12345 Market St";
49+
address.city = "San Francisco";
50+
address.state = "CA";
51+
address.zip = "94114";
52+
```
53+
54+
Plain objects are by default `[[Prototype]]` linked to `Object.prototype`, giving them delegated access to several general object methods, such as:
55+
56+
* `toString()` / `toLocaleString()`
57+
* `valueOf()`
58+
* `isPrototypeOf(..)`
59+
* `hasOwnProperty(..)` (recently deprecated -- alternative: static `Object.hasOwn(..)` utility)
60+
* `propertyIsEnumerable(..)`
61+
* `__proto__` (getter function)
62+
63+
```js
64+
address.isPrototypeOf(Object.prototype); // true
65+
address.isPrototypeOf({}); // false
66+
```
2567

2668
## Fundamental Objects
2769

28-
In Chapter 2, I briefly mentioned *auto-boxing*, which we'll now revisit. [^AutoBoxing]
70+
JS defines several *fundamental* object types, which are instances of various built-in constructors, including:
71+
72+
* `new String()`
73+
* `new Number()`
74+
* `new Boolean()`
75+
76+
Note that these constructors must be used with the `new` keyword to construct instances of the fundamental objects. Otherwise, these functions actually perform type coercion (see Chapter 4).
77+
78+
These fundamental object constructors create object value-types instead of a primitives:
79+
80+
```js
81+
myName = "Kyle";
82+
typeof myName; // "string"
83+
84+
myNickname = new String("getify");
85+
typeof myNickname; // "object"
86+
```
87+
88+
In other words, an instance of a fundamental object constructor can actually be seen as a wrapper around the corresponding underlying primitive value.
89+
90+
| WARNING: |
91+
| :--- |
92+
| It's nearly universally regarded as *bad practice* to ever directly instantiate these fundamental objects. The primitive counterparts are generally more predictable, more performant, and offer *auto-boxing* (see "Automatic Objects" section below) whenever the underlying object-wrapper form is needed for property/method access. |
93+
94+
The `Symbol(..)` and `BigInt(..)` functions are referred to in the specification as "constructors", though they're not used with the `new` keyword, and the values they produce in a JS program are indeed primitives.
95+
96+
How, there are internal *fundamental objects* for these two types, used for prototype delegation and *auto-boxing*.
97+
98+
By contrast, for `null` and `undefined` primitive values, there aren't `Null()` or `Undefined()` "constructors", nor corresponding fundamental objects or prototypes.
99+
100+
### Prototypes
101+
102+
Instances of the fundamental object constructors are `[[Prototype]]` linked to their constructors' `prototype` objects:
103+
104+
* `String.prototype`: defines `length` property, as well as string-specific methods, like `toUpperCase()`, etc.
105+
106+
* `Number.prototype`: defines number-specific methods, like `toPrecision(..)`, `toFixed(..)`, etc.
107+
108+
* `Boolean.prototype`: defines default `toString()` and `valueOf()` methods.
109+
110+
* `Symbol.prototype`: defines `description` (getter), as well as default `toString()` and `valueOf()` methods.
111+
112+
* `BigInt.prototype`: defines default `toString()`, `toLocaleString()`, and `valueOf()` methods.
113+
114+
Any direct instance of the built-in constructors have `[[Prototype]]` delegated access to its respective `prototype` properties/methods. Moreover, corresponding primitive values also have such delegated access, by way of *auto-boxing*.
115+
116+
### Automatic Objects
117+
118+
I've mentioned *auto-boxing* several times (including Chapters 1 and 2, and a few times so far in this chapter). It's finally time for us to explain that concept.
119+
120+
Accessing a property or method on a value requires that the value be an object. As we've already seen in Chapter 1, primitives *are not* objects, so JS needs to then temporarily convert/wrap such a primitive to its fundamental object counterpart[^AutoBoxing] to perform that access.
121+
122+
For example:
123+
124+
```js
125+
myName = "Kyle";
126+
127+
myName.length; // 4
128+
129+
myName.toUpperCase(); // "KYLE"
130+
```
131+
132+
Accessing the `length` property or the `toUpperCase()` method, is only allowed on a primitive string value because JS *auto-boxes* the primitive `string` into a wrapper fundamental object, an instance of `new String(..)`. Otherwise, all such accesses would have to fail, since primitives do not have any properties.
133+
134+
More importantly, when the primitive value is *auto-boxed* to its fundamental object counterpart, those internally created objects have access to predefined properties/methods (like `length` and `toUpperCase()`) via a `[[Prototype]]` link to their respective fundamental object's prototype.
135+
136+
So an *auto-boxed* `string` is an instance of `new String()`, and is thus linked to `String.prototype`. Further, the same is true of `number` (wrapped as an instance of `new Number()`) and `boolean` (wrapped as an instance of `new Boolean()`).
137+
138+
Even though the `Symbol(..)` and `BigInt(..)` "constructors" (used without `new`produce primitive values, these primitive values can also be *auto-boxed* to their internal fundamental object wrapper forms, for the purposes of delegated access to properties/methods.
139+
140+
| NOTE: |
141+
| :--- |
142+
| See the "Objects & Classes" book of this series for more on `[[Prototype]]` linkages and delegated/inherited access to the fundamental object constructors' prototype objects. |
143+
144+
Since `null` and `undefined` have no corresponding fundamental objects, there is no *auto-boxing* of these values.
145+
146+
A subjective question to consider: is *auto-boxing* a form of coercion? I say it is, though some disagree. Internally, a primitive is converted to an object, meaning a change in value-type has occurred. Yes, it's temporary, but plenty of coercions are temporary. Moreover, the conversion is rather *implicit* (implied by the property/method access, but only happens internally). We'll revisit the nature of coercion in Chapter 4.
147+
148+
## Other Built-in Objects
149+
150+
In addition to fundamental object constructors, JS defines a number of other built-in constructors that create further specialized object sub-types:
151+
152+
* `new Date(..)`
153+
* `new Error(..)`
154+
* `new Map(..)`, `new Set(..)`, `new WeakMap(..)`, `new WeakSet(..)` -- keyed collections
155+
* `new Int8Array(..)`, `new Uint32Array(..)`, etc -- indexed, typed-array collections
156+
* `new ArrayBuffer(..)`, `new SharedArrayBuffer(..)`, etc -- structured data collections
157+
158+
## Arrays
159+
160+
Arrays are objects that are specialized to behave as numerically indexed collections of values, as opposed to holding values at named properties like plain objects do.
161+
162+
Arrays have a literal form:
163+
164+
```js
165+
favoriteNumbers = [ 3, 12, 42 ];
166+
167+
favoriteNumbers[2]; // 42
168+
```
169+
170+
The same array could also have been defined imperatively using the `new Array()` constructor:
171+
172+
```js
173+
favoriteNumbers = new Array();
174+
favoriteNumbers[0] = 3;
175+
favoriteNumbers[1] = 12;
176+
favoriteNumbers[2] = 42;
177+
```
178+
179+
Arrays are `[[Prototype]]` linked to `Array.prototype`, giving them delegated access to a variety of array-oriented methods, such as `map(..)`, `includes(..)`, etc:
180+
181+
```js
182+
favoriteNumbers.map(v => v * 2);
183+
// [ 6, 24, 84 ]
184+
185+
favoriteNumbers.includes(42); // true
186+
```
187+
188+
Some of the methods defined on `Array.prototype` -- for example, `push(..)`, `pop(..)`, `sort(..)`, etc -- behave by modifying the array value in place. Other methods -- for example, `concat(..)`, `map(..)`, `slice(..)` -- behave by creating a new array to return, leaving the original array intact. A third category of array functions -- for example, `indexOf(..)`, `includes(..)`, etc -- merely computes and returns a (non-array) result.
189+
190+
## Regular Expressions
29191

30192
// TODO
31193

194+
## Functions
195+
196+
// TODO
197+
198+
## Proposed: Records/Tuples
199+
200+
At the time of this writing, a (stage-2) proposal[^RecordsTuplesProposal] exists to add a new set of features to JS, which correspond closely to plain objects and arrays, but with some notable differences.
201+
202+
Records are similar to plain objects, but are immutable (sealed, read-only), and (unlike objects) are treated as primitive values, for the purposes of value assignment and equality comparison. The syntax difference is a `#` before the `{ }` delimiter. Records can only contain primitive values (including records and tuples).
203+
204+
Tuples have exactly the same relationship, but to arrays, including the `#` before the `[ ]` delimiters.
205+
206+
It's important to note that while these look and seem like objects/arrays, they are indeed primitive (non-object) values.
207+
208+
[^FundamentalObjects]: "20 Fundamental Objects", EcamScript 2022 Language Specification; https://262.ecma-international.org/13.0/#sec-fundamental-objects ; Accessed August 2022
209+
32210
[^AutoBoxing]: "6.2.4.6 PutValue(V,W)", Step 5.a, ECMAScript 2022 Language Specification; https://262.ecma-international.org/13.0/#sec-putvalue ; Accessed August 2022
211+
212+
[^RecordsTuplesProposal]: "JavaScript Records & Tuples Proposal"; Robin Ricard, Rick Button, Nicolò Ribaudo;
213+
https://github.com/tc39/proposal-record-tuple ; Accessed August 2022

types-grammar/toc.md

+9-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
* Foreword
1010
* Preface
11-
* Chapter 1: Primitives
11+
* Chapter 1: Primitive Values
1212
* Value Types
1313
* Empty Values
1414
* Boolean Values
@@ -17,15 +17,21 @@
1717
* BigInteger Values
1818
* Symbol Values
1919
* Primitives Are Built-In Types
20-
* Chapter 2: Value Behaviors
20+
* Chapter 2: Primitive Behaviors
2121
* Primitive Immutability
2222
* Primitive Assignments
2323
* String Behaviors
2424
* Number Behaviors
2525
* Primitives Are Foundational
2626
* Chapter 3: Object Values
27-
* Nature Of Objects
27+
* Types of Objects
28+
* Plain Objects
2829
* Fundamental Objects
30+
* Other Built-in Objects
31+
* Arrays
32+
* Regular Expressions
33+
* Functions
34+
* Proposed: Records/Tuples
2935
* TODO
3036
* Chapter 4: Coercing Values
3137
* Coercion: Explicit vs Implicit

0 commit comments

Comments
 (0)