Skip to content

Commit

Permalink
types+grammar: clarifications around 'primitive', closes getify#271
Browse files Browse the repository at this point in the history
  • Loading branch information
getify committed Dec 27, 2014
1 parent 7532b66 commit 93ca2c0
Show file tree
Hide file tree
Showing 3 changed files with 16 additions and 14 deletions.
18 changes: 10 additions & 8 deletions types & grammar/ch1.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ Coercion confusion is perhaps one of the most profound frustrations for JavaScri

Armed with a full understanding of JavaScript types, we're aiming to illustrate why coercion's *bad reputation* is largely over-hyped and somewhat undeserved -- to flip your perspective, to seeing coercion's power and usefulness. But first, we have to get a much better grip on values and types.

## Primitives
## Built-in Types

JavaScript defines seven built-in types, that we often call "primitives". These are:
JavaScript defines seven built-in types. These are:

* `null`
* `undefined`
Expand All @@ -45,7 +45,9 @@ JavaScript defines seven built-in types, that we often call "primitives". These
* `object`
* `symbol` -- added in ES6!

The `typeof` operator inspects the type of the given value, and always returns one of seven string values (though, strangely, there's not an exact 1-to-1 match with the seven primitive types we just listed).
**Note:** All of these types except `object` are called "primitives".

The `typeof` operator inspects the type of the given value, and always returns one of seven string values -- surprisingly, there's not an exact 1-to-1 match with the seven built-in types we just listed.

```js
typeof undefined === "undefined"; // true
Expand All @@ -58,15 +60,15 @@ typeof { life: 42 } === "object"; // true
typeof Symbol() === "symbol"; // true
```

These 6 listed types have values of the corresponding type and return a string of the same name, as shown. `Symbol` is a new data type as of ES6, and will be covered later.
These 6 listed types have values of the corresponding type and return a string value of the same name, as shown. `Symbol` is a new data type as of ES6, and will be covered in Chapter 3.

As you may have noticed, I excluded `null` from the above listing. It's *special* -- special in the sense that it's buggy.
As you may have noticed, I excluded `null` from the above listing. It's *special* -- special in the sense that it's buggy when combined with the `typeof` operator:

```js
typeof null === "object"; // true
```

It would have been nice (and correct!) if it returned `"null"`, but this original bug in JS has persisted for nearly 2 decades, and will likely never be fixed because there's too much existing web content that relies on its buggy behavior that "fixing" the bug would *create* "more bugs" and break a lot of web software.
It would have been nice (and correct!) if it returned `"null"`, but this original bug in JS has persisted for nearly 2 decades, and will likely never be fixed because there's too much existing web content that relies on its buggy behavior that "fixing" the bug would *create* more "bugs" and break a lot of web software.

If you want to test for a `null` value using its type, you need a compound condition:

Expand All @@ -78,13 +80,13 @@ var a = null;

`null` is the only value that is "falsy" (aka false-like; see Chapter 4) but that also returns `"object"` from the `typeof` check.

So what's the seventh string value that `typeof` can return? And why is it not actually a top-level type?
So what's the seventh string value that `typeof` can return?

```js
typeof function a(){ /* .. */ } === "function"; // true
```

It's easy to think that `function` would be a top-level primitive type in JS, especially given this behavior of the `typeof` operator. However, if you read the spec, you'll see it's actually somewhat of a "subtype" of object. Specifically, a function is referred to as a "callable object" -- an object that has an internal `[[Call]]` property that allows it to be invoked.
It's easy to think that `function` would be a top-level built-in type in JS, especially given this behavior of the `typeof` operator. However, if you read the spec, you'll see it's actually a "subtype" of object. Specifically, a function is referred to as a "callable object" -- an object that has an internal `[[Call]]` property that allows it to be invoked.

The fact that functions are actually objects is quite useful. Most importantly, they can have properties. For example:

Expand Down
10 changes: 5 additions & 5 deletions types & grammar/ch2.md
Original file line number Diff line number Diff line change
Expand Up @@ -846,11 +846,11 @@ d; // [1,2,3,4]

Simple values (aka scalar primitives) are *always* assigned/passed by value-copy: `null`, `undefined`, `string`, `number`, `boolean`, and ES6's `symbol`.

Compound primitives -- `object` (including `array`s, and all boxed object wrappers -- see Chapter 3) and `function` -- *always* create a copy of the reference on assignment or passing.
Compound values -- `object` (including `array`s, and all boxed object wrappers -- see Chapter 3) and `function` -- *always* create a copy of the reference on assignment or passing.

In the above snippet, because `2` is a scalar primitive, `a` holds one initial copy of that value, and `b` is assigned another *copy* of the value. When changing `b`, you are in no way changing the value in `a`.

But **both `c` and `d`** are seperate references to the same shared value `[1,2,3]`, which is a compound primitive. It's important to note that neither `c` nor `d` more "owns" the `[1,2,3]` value -- both are just equal peer references to the value. So, when using either reference to modify (`.push(4)`) the actual shared `array` value itself, it's affecting just the one shared value, and both references will reference the newly modified value `[1,2,3,4]`.
But **both `c` and `d`** are seperate references to the same shared value `[1,2,3]`, which is a compound value. It's important to note that neither `c` nor `d` more "owns" the `[1,2,3]` value -- both are just equal peer references to the value. So, when using either reference to modify (`.push(4)`) the actual shared `array` value itself, it's affecting just the one shared value, and both references will reference the newly modified value `[1,2,3,4]`.

Since references point to the values themselves and not to the variables, you cannot use one reference to change where another reference is pointed:

Expand Down Expand Up @@ -916,15 +916,15 @@ As you can see, `x.length = 0` and `x.push(4,5,6,7)` were not creating a new `ar

Remember: you cannot directly control/override value-copy vs. reference -- those semantics are controlled entirely by the type of the underlying value.

To effectively pass a compound primitive (like an `array`) by value, you need to manually make a copy of it, so that the reference passing doesn't modify the original. For example:
To effectively pass a compound value (like an `array`) by value, you need to manually make a copy of it, so that the reference passing doesn't modify the original. For example:

```js
foo( a.slice() );
```

`Array#slice(..)` with no parameters by default makes an entire (shallow) copy of the `array`. So, we pass in a reference only to the copied `array`, and thus `foo(..)` cannot affect the contents of `a`.

To do the reverse -- pass a scalar primitive value in a way where its value updates can be seen, kinda like a reference -- you have to wrap the value in another compound primitive (`object`, `array`, etc) which *can* be passed by reference:
To do the reverse -- pass a scalar primitive value in a way where its value updates can be seen, kinda like a reference -- you have to wrap the value in another compound value (`object`, `array`, etc) which *can* be passed by reference:

```js
function foo(wrapper) {
Expand Down Expand Up @@ -981,4 +981,4 @@ The `null` type has just one value: `null`, and likewise the `undefined` type ha

`number`s include several special values, like `NaN` (supposedly "not a `number`", but really more appropriately "invalid `number`"); `+Infinity` and `-Infinity`; and `-0`.

Simple scalar primitives (`string`s, `number`s, etc.) are assigned/passed by value-copy, but compound primitives (`object`s, etc.) are assigned/passed by reference. References are **not** like references/pointers in other languages -- they're never pointed at other variables/references, only at the underlying values.
Simple scalar primitives (`string`s, `number`s, etc.) are assigned/passed by value-copy, but compound values (`object`s, etc.) are assigned/passed by reference. References are **not** like references/pointers in other languages -- they're never pointed at other variables/references, only at the underlying values.
2 changes: 1 addition & 1 deletion types & grammar/toc.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* Preface
* Chapter 1: Types
* A Type By Any Other Name...
* Primitives
* Built-in Types
* Values As Types
* Chapter 2: Values
* Strings
Expand Down

0 comments on commit 93ca2c0

Please sign in to comment.