Closed the-simian closed 10 years ago
FYI: chapter 4 still in flux. I'd focus on 1-3 for now. 4 should be done in the next couple of days. Thanks! :)
Ch 1 types
There is a lot of really really good stuff in this chapter I agree with very strongly, and think is valuable information:
symbol
, to be honest, and can't really comment on them. I can read they as "private properties" to generic objects (which sounds awesome), but I'm going to withhold any opinion about them, as I don't feel I've worked with them enough to foster a well informed opinion. This is simply because my experience in es5 is much greater, as it is my day to day coding, and the core of my knowledgebase.I only had one thing I think needs more attention:
I should add this is a fairly minor thing, but kind of requires a long explanation to express why I feel as I do, so please don't mistake a wall of text for a fiercely antagonistic disposition to this point, I am merely trying to express what is a kind of a nuanced point. I also posses apparently a minority opinion, so I'm probably wrong but here it goes:
The only one thing in this section I disagree with: typeof null
being "object" is a "bug".
Kyle immediately points out correctly that null is the only way to essentially have a falsy object. This is the backbone of my opinion.
As I see it, every primitive type has both truthy and falsy values. The exception is undefined
, and for good reason. It is lack of intent; nothing has been assigned. Truthiness does not work conceptually with this type in any way.
Type | Falsy Value | Truthy Value |
---|---|---|
undefined | undefined | makes no conceptual sense |
boolean | false | true |
string | '' | 'anything' |
number | 0, NaN | 1, Infinity |
object | null | {} |
So firstly we can see that things besides actual numerical values, like 1 and 2 can be a number (NaN, Infinity). So null being a typeof
object, doesn't break suit from this pattern, only reinforces that values usually conform to the existing types. (there is no typeof Infinity
= "infinity")
Secondly, the reasoning, as I understand is that null, in contrast to undefined
was, and still is used when some value has been set, intentionally to a 'non value' as opposed to haven't been assigned yet. Often times, an empty reference to an object. So this is the intentional absence of any object value, and without this being typeof object
, this expressiveness wouldn't exist.
Consider:
[{}.{},{}, null,{}]
and
[{},{},{}, undefined, {}]
In these situations one needs to express a list of objects, and one is falsy - a 'non' value, but is intentionally assigned as such. one might say 'simply use false'. To use false
would imply heterogeneous typing, and is subtly different from null. Or fine, use null
, but it has a different type ( a type of "null"). Again, we want a falsy object
, a list of all items are the same type, and can be treated as such, processed as such by functions/ logic in our code. Something of a different type might intentionally throw an error. A 'falsy object' might have meaning, such as a point in a step when someone explicitly did not select something, but the selection process did occur. (before the selection process it would be undefined). This would be like ''
be the same as leaving a text box blank, or NaN
the explicit result of parsing something besides a numerical value like 1,2,3,4.
To be more clear consider these:
[1,2, 3,NaN, 5] //for numbers
['a', 'b', 'c', '', 'e'] //for strings
[true, true, true, false, true] //bools
The examples above express a homogenous list of types and an intentional falsy value, as contrast with:
[1,2, undefined, 4] //missing value, was never assigned.
Aside from practice, my worldview about the null
value's typeof is also informed by this:
4.3.11 null value primitive value that represents the intentional absence of any object value
and
4.3.9 undefined value primitive value used when a variable has not been assigned a value
That is from the Ecma-262 spec.
I should add that Kyle stating that 'its a bug' is not uncommon , if anything I am taking the minority position with my interpretation of the spec, and when its appropriate to use null vs undefined. Most folks seem to repeat that "its just a bug, forget it, even Brendan Eich says so". (Usually this is followed by a link to Axel Rauchmayer's blog/ famous stack overflow question, or MDN (which just points right back to Axel's blog... again)).
Actually one of the oldest sources of this is On Crockfords site, where He also points out typeof Array being 'object' is also wrong. (but Array is an object- its not like a primitive!); Its down at "Mistakes were made". He even has "capital" String as typeof "string". The primitive (lowercase) string is... but (capital) String() is typeof
"object". Basically this table doesn't really differentiate Natives/Constructors from primitives.
So? Very Smart People™ disagree. Maybe I am in the wrong, but based on what I've presented above, this seems logical to me, and consistent with the rest of the language. Even if I am super duper wrong, this is unclear enough it could probably be explained more.
To be fair, there stuff like this further down the spec:
Semantics The value of the null literal null is the sole value of the Null type, namely null. and
4.3.12 Null type type whose sole value is the null value
Which doesn't at all make things more clear. In one place it says null type/value is null, and the other its an intentional absence of an object value (which makes it more like NaN). Whats right? Well I do know null is actually a primitive in how it is implemented in the language, but is returning "object" on typeof really wrong?
Based on my previous points, I feel like going with the first description seems more logical. Making the typeof null
as "null
adds an unnecessary typeof
return type, that breaks suit from the current pattern (eg NaN, Infinity), and reduces the expressiveness of the language by omitting falsy objects. In other words should typeof NaN
be "NaN" instead of number? How do you express 'falsy object'?
From this treatment, Null
is a value (in practice), not a type (even thought it really is a primitive type), much like Infinity
is a value and not a type. The respective types are object
, number
.
I'm aware null is not really an object (as you cannot add or edit properties), but one cannot do math with NaN or Infinity, either. They're not really numbers, but it makes sense to put them that 'type'.
var obj = {};
obj instanceof Object
//true
var nil = null;
nil instanceof Object
//false
This always seemed to make sense, because even though null is a primitive, and also an "object value", that means typeof is fine, but it is not an instanceof Object. Suddenly, this makes 4.3.12 "work", categorizing it as object value type, but not an instance of object (because you cannot put properties on it, etc).
In other words, from that perspective with typeof null
being "object"; is fine like it is, and is like it is for a good reason, and instanceof Object yielding false is also right.
But this might just be the equivalent of the bootstrap crowd hijacking, and re-implementing the <i>
html tag to informally mean "icon" instead of "italic". When the spec is unclear, or dated or just plain bad, developers tend to try to fix it in practice, and for me in practice null
is about "null object" (as a value).
A final thought : I remember reading about this some time ago, I found a mailing list about it, with - Axel Rauschmayer, Brendan Eich, Rick Waldron, Mikeal Rogers, and a whole bunch of Smart People™. I decided to read it for fun. I found it again, here . Subject? typeofnull. At one point someone interjects
To me typeof null === 'object' is fine. It makes null a value in the space of 'object'. In practice I see 'null' used to mean "I know this reference (usually an argument) should be an object; I want to pass nothing but signal that I really did mean to pass nothing." The status quo allows this and it seems enough work for null to do for us.
Yes. That. That's what I think, at least that seems logical to me..
TL;DR Kyle is right by most of the world about typeof null
is "object" is a bug, but in my opinion it does not seem illogical that it is "object", and in many ways makes more sense (to me) with null
as a null value in practice. At the very lease there is an opportunity in this book to address some of this level of confusion about null, which everyone can agree is somewhat unclear.
Just got through testing all the code samples in Chrome/Firefox/ JSFiddle. Haven't gotten to IE, but I'm sure they're fine.
Okay, thanks I'll keep that in mind and do 1-3 only.
Chapter 2: Values
10..makeItRain()
= hilariousparseInt()
to pop up somewhere, along with a warning about not forgetting your radix.n | 0
operator for forcing 32-bit signed integers, might mention the ~~ "poor mans Math.floor()" > This could be the wrong time and place, too.undefined
in non strict mode. Wow. That's terrifying.Testing
The code ran as described
Ch3 : Natives
Testing
All the code ran as described.
This is all fantastic feedback and review comments! Thanks so much. Will take a bit to digest and address the various points, but I will make sure to do so. Keep it coming!
On the topic of typeof null == "object"
, I appreciate your in-depth thoughts and reasoning.
Indeed, when I first was doing JS training workshops, I also called null
a "special kind of object reference". Not for the reasons you mention, but just so that I could explain the behavior that typeof
returned "object"
, but that you can't visibly get a reference to a null
value, at least not obviously. I used to explain these behaviors as: "there's only one special built-in null
value, and every time you assign it, you're assigning by reference, etc."
Then Brendan Eich called me out on twitter with the "No, no, it's just a bug." That was enough to snap me out of my disillusion. :)
But more seriously, let me try to address a few of your assertions briefly:
"4.3.11 null value - primitive value that represents the intentional absence of any object value" yeah, that's kinda troubling. TBH, I'd never noticed that "object value" part. I dunno what's up with that. Seems like it's perhaps legacy language.
But moreover, I think it's important to note that the wording here seems to deviate from the other types, and not necessarily say what null
is but more how you might use null
. That's subtle, but I think it's enough to water it down to "suggested semantics" rather than "absolute identification".
In other words, it seems like "a valid suggested way to use null
is to represent the absence of an object reference." That's by no means the only way to use null
, but it's interesting to note that the spec calls it out as a (at least) a suggestion.
Ironically, further support for this wording being a "suggestion" comes from your quote of the undefined
value:
primitive value used when a variable has not been assigned a value
Clearly, this is one way to use undefined
, but there's all kinds of places where that's not a complete description. For example, the x = void 1
operator results in assigning the undefined
value to x
, not "not assiging a value". Similarly, x = function(){}()
. In both cases, if x
had a value before, it no longer has it, and instead has the undefined
value. That's subtly but importantly different to "has not been assigned a value".
So, again, that wording has to be taken as (at most) "suggested semantics", not exclusive, prescriptive usage.
OTOH, another (I still think perfectly valid) way to use null
is to use it as an indistinguishable "alias" of undefined
. That's the camp I'm in. I treat both null
and undefined
as "the absence or emptiness of value". I have seen people try to split them, like "undefined
is never had a value yet and null
is had a value but doesn't anymore", or even "undefined
is has no value and null
is has the empty value".
In my experience, while such distinctions can be made, the benefit of making such distinctions is so weak as to not rise to the level of relevance in my code. In other words, "undefined
is the same as null
" always seems to me to have a stronger impact on the clarity and readability of my code than the other nuances that I could choose to rely on.
"Often times, an empty reference to an object" Often times? What then do we say about: x = 3; x = null;
? Is that an inappropriate use of null
, because it's not replacing a previous object reference with an empty object reference, but is rather just replacing a primitive 3
value?
I suppose you could argue that... that you should only use x = null
to "unset" object references, and should use x = undefined
to clear out non-object-references. But I've never found that to be a common idiom, and it doesn't seem like that splitting of hairs is going to significantly improve type reasoning in a non-type-enforced language like JS. It seems overly rigorous for not much benefit.
Maybe I'm missing more nuance here?
Moreover, the reasoning that you imply that null
is the typed version of the absence of an object value (as compared to undefined
being the typed version of the absence of non-object values) is a little strange to me.
Let me ask it this way: is the absence of light a form of light we'd call "missing light"? I doubt it. I think we'd say, "either there is light, or there isn't light." Weak analogy, I know, but it's the best I could come up with so far.
With respect to JavaScript, I don't think it makes sense to talk about the "type of emptiness". It just is "empty". That's why I treat null
and undefined
as indistinguishable (see above).
"Null is a value (in practice), not a type" This doesn't make sense to me. null
is both a value and a type.
Presently, we're discussing its behavior as a type (the "why or in what circumstances do we use this type" implicit question at hand). That's why our vehicle for the discussion was the typeof null
behavior.
So, the question really is, what do we use the null
type for? Once we answer that (subjectively, for ourselves), we then turn to the null
value to accomplish it.
Boiling all this down, here's my leaning at the moment:
null
a type, I still think typeof null == "object"
is most accurately described as a bug.However...
At the very lease [sic] there is an opportunity in this book to address some of this level of confusion about null, which everyone can agree is somewhat unclear.
Yep, I think there's enough nuance here that warrants me adding an explanatory note that covers the different perspectives, rather than just glibly glossing over it entirely as I've currently done.
In fact, I think (2) may need to be a section in an appendix, which I will reference from Chapter 1 in a small side note. That way, it doesn't interrupt the flow and reasoning of chapter 1, but it adequately offers deeper explanation for those who care (like you!). :)
don't know much about symbol
Yeah, they're brand new. The YDKJS books have to walk a fine line striding between the ES5 world and the (soon coming) ES6 world. Overall, my approach thus far has to sway more toward ES5, but to cover important ES6 things when a contrast is instructive. The fifth title is going to be exclusively "ok, here's all the new world as of ES6", so no straddling there. It'll assume a fully immersive ES6 mindset.
I can read they [sic] as "private properties" to generic objects
Actually, they're not really "private properties" by behavior. The can easily be revealed. That's already some misconception that's spreading (several inaccurate blog posts), and ES6 isn't even out yet!
I'll be covering them more in the fifth title, but there's a brief treatment of them in Chapter 3. They are "special-use properties" more than "private properties". But, admittedly, many devs will probably use them to replace __xyz
type prefixed properties, which are usually idiomatically called "private properties".
Your number explanation was good, but I expected parseInt() to pop up somewhere, along with a warning about not forgetting your radix.
That's fully covered in Chapter 4. I'll see if there's an appropriate place to insert a note into chapter 2 to reference the coverage in chapter 4.
might mention the ~~ "poor mans Math.floor()"
Good catch, this should definitely be mentioned.
This whole chapter is a list of things not to do with Native Constructors.
The intent of this chapter is to say, generally: "Don't use these Natives as Constructors", but that's entirely because it tees up Chapter 4 to show why the better use of these Natives is as explicit coercions. Number("42"); // 42
is so much better than new Number("42"); // object
. :)
Thanks for responding so fast, I didn't have time to really proof everything I had composed (I was sort of working in the markdown). Nonetheless, it seems like you've gotten the big picture of What I'm trying to express, and apologies for any typos/sloppiness on my end.
ok, chapter 4 is "done" and ready for review. thanks for your patience! :)
note: ch4 has had heavy changes across the entire chapter, over the last week, so make sure to a take a fresh eye to the whole thing.
ping. ch4 and ch5 are ready for review.
Thanks for the update! :+1:
one last ping! ch5 and now appendix-A are ready for review, if you get a chance. :)
Chapter 4 Review:
I read this section two times, here are some thoughts I had while reading.
You didn't mention incrementors and decrementors:
+"4" //4 ..coerced
++"4" //Error
"4"++ //Error
but...
var g ="4";
++g = //5 ..coerced and incremented
var g ="4";
g++ //4 ..coerced, not incremented.
and
var g ="4";
+g++ //4 ..coerced, not incremented.
var g ="4";
++g++ //4 ..Error.
I only bring this up because in addition to concatination, addition and unary +
coercion, incrementors and decrementors look very similar and can be unclear. Imagine all your "this is crazy don't write it" examples with ++
and --
also thrown in for good measure.
This matters more if the string to be coerced has no decimal. your example with "3.14"
doesn't exploit this behavior as obviously
//spaces in places.
var a = "4";
var b = 9;
var c = b +++a;
//13
var a = "4";
var b = 9;
var c = b + ++a;
//14
var a = "4";
var b = 9;
var c = b + + +a;
//94
//Hyperbolically bad:
var c = "3";
var d = +5+- + +-c++;
//8
var minute = parseInt( selectedMiniute.value );
selectedMinute is misspelled. This doesn't break the code, or the point of the section , but since we're looking for this sort of thing.var b = a ? true : false;
Is a correct alternative; therefore, b=!!a
(assuming at the end of the day you really need a Boolean). It seems you're pointing out that you're really doing (!!42):true : false
,thus doing true? true: false
and that is meaningless. Maybe more code examples can reinforce the point here?the parseInt( 1/0, 19 );
is excellent. Honestly I am reminded of a talk called WAT that was floating around that was specifically exploiting the act of performing concatination with implicitly coercing things like [] + []
, [] + {}
, {} + []
, {} + {}
. I bring this up because this section seems like it is more or less refuting so-called unpredictable behavior by pointing out why it works as such. In the above examples I listed, they are relevant because of the order they seem to be coerced, which also might not be predictable at first. [] + [] //""
because these get coerced ToString, and you concatinate "" and "" to get "".
but this is where order matters...
{} + [] //0
[] + {} //"[object Object]"
{} + {} // NaN
we know an object turns into "[object Object]"
var k = {};
k.toString(); "[object Object]"
and we know an array becomes ""
[].toString() //""
so this seems logical.
[] + {} //"[object Object]"
because you'd be concatenating "" and "[object Object]"
But in these cases.. the + isn't just simply concatenating strings, its actually acting like the aforementioned unary +
operator and doing implicit coersion.
{} + [] //0
{} + {} // NaN
here is the culprit, its the implicit ToNumber conversion
+[] //0
+{} //NaN
but note it is coercing the second value before the first one. Its not doing it in the order that we read from left to right.
so in example 1( {} + []
) its actually {} + 0
before it is Nan + []
This section is really large, and you've already dedicated a ton of time to "funky stuff". Maybe pointing out the order of coercion above is just extra noise adding to the choir of "look at this odd behavior". I know the whole point of this section is to highlight that coercion is actually very predictable, and useable and for that I am very thankful. Either way, I figured I'd at least bring this up in review, and if you find it not useful then that makes sense too.
`==true
and ==false
in conditionals (which are usually pointless) and the "onleOne" code snippet, whereby arguments are summed and any value exceeding 0
is truthy. I don't know if this is a good time to mention this, as it may be the wrong place or time or section, but if one wants to actually do "fuzzy" object comparison, they would need to write some custom method to achieve that goal.
Something like this?
function objectsAreEqual(x, y) {
// I've heavily commented this because equality for js objects is tricky - Jesse
if (x === y) {
// if both x and y are null or undefined and exactly the same
// short circuits primitives.
return true;
}
if (!(x instanceof Object) || !(y instanceof Object)) {
// if they are not strictly equal, they both need to be Objects
// instanceof preferable to typeof here.
return false;
}
if (x.constructor !== y.constructor) {
// they must have the exact same prototype chain, the closest we can do is
// test for a constructor.
return false;
}
for (var p in x) {
if (!x.hasOwnProperty(p)) {
// other properties were tested using x.constructor === y.constructor
continue;
}
if (!y.hasOwnProperty(p)) {
// allows to compare x[ p ] and y[ p ] when set to undefined
return false;
}
if (x[p] === y[p]) {
// if they have the same strict value or identity then they are equal
continue;
}
if (typeof(x[p]) !== 'object') {
// Numbers, Strings, Functions, Booleans must be strictly equal
return false;
}
if (!Object.equals(x[p], y[p])) {
// Objects and Arrays must be tested recursively
return false;
}
}
for (p in y) {
if (y.hasOwnProperty(p) && !x.hasOwnProperty(p)) {
// allows x[ p ] to be set to undefined
return false;
}
}
return true;
}
Maybe that isn't the way you recommend, and again this might be the wrong place, but since you're discussing ==
and ===
s relationship with objects, I thought Id at least mention it.
Chapter 4 continued
I should add that for me all the code examples acted as expected. I didn't run them all in ancient versions of IE, but I've encountered the 'radix problem' and other things pre ES5 often enough that I know your examples are as advertised.
The sections build upon each very well. For example, you teach the reader about JSON.stringify
and toJSON
early on so when those concepts return (such as in Explicitly: * --> Boolean), everything makes a lot of sense.
Best of all this chapter is about what makes Javascript good rather than categorizing it as simple "deeply flawed".
I shudder at thinking JS should start throwing errors all over the place so that try..catch is needed around almost every line.
As do I. Do you want to drive a motorcycle or a clunky minivan with armor welded on?
The big picture:
This is probably the best written body of work on coercion I've ever read. Its long, but that's OK. Coercion is a big deal in Javascript. The entire tone of teaching the developer how things work rather than only espousing a list of 'naughty things to never do' is really effective.
Thanks, as always, for your fantastic and detailed review comments! :)
You didn't mention incrementors and decrementors
See Chapter 5, "Expression Side Effects". I may insert a forward-referencing note to Ch5 here.
+++a; ...... + ++a; ...... + + +a
These are interesting examples I hadn't thought to point out. I may try to mention something brief like that in a note, but definitely in the spirit of "don't do this!".
[] + [] ...... {} + [] ...... [] + {} ...... {} + {}
See Chapter 5, "Blocks". I may insert a forward-referencing note to Ch5 here.
Maybe that isn't the way you recommend, and again this might be the wrong place
Perhaps instead of showing code like this, I should insert a note that many JS libs/frameworks have options for "deepEquality" and explain briefly how they do that. What do you think? Effective enough to clarify the point?
FYI: one more substantial section I just added to chapter 4, if you want to glance at it:
If you have any more review comments to add, please re-open and do so! :)
My review of types and grammar.
Edit: I'm coming back for a second pass on these first three for proofing, etc.