Can somebody explain how John Resig's pretty.js JavaScript works?
http://ejohn.org/files/pretty.js
// Takes an ISO time and returns a string representing how
// long ago the date represents.
function prettyDate(time){
var date = new Date((time || "").replace(/-/g,"/").replace(/[TZ]/g," ")),
diff = (((new Date()).getTime() - date.getTime()) / 1000),
day_diff = Math.floor(diff / 86400);
if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 )
return;
return day_diff == 0 && (
diff < 60 && "just now" ||
diff < 120 && "1 minute ago" ||
diff < 3600 && Math.开发者_运维问答floor( diff / 60 ) + " minutes ago" ||
diff < 7200 && "1 hour ago" ||
diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") ||
day_diff == 1 && "Yesterday" ||
day_diff < 7 && day_diff + " days ago" ||
day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago";
}
// If jQuery is included in the page, adds a jQuery plugin to handle it as well
if ( typeof jQuery != "undefined" )
jQuery.fn.prettyDate = function(){
return this.each(function(){
var date = prettyDate(this.title);
if ( date )
jQuery(this).text( date );
});
};
How exactly is the prettyDate()
method returning a string? Is this another one of those 'strange' things you can do in JavaScript or am I just missing something?
edit: I didn't ask how he's returning a value, I asked how he's returning a string.
return day_diff == 0 && (....)
returns a boolean in any language I've ever used.
In JavaScript:
a || b
is equivalent toa ? a : b
a && b
is equivalent toa ? b : a
- any non-empty string in a boolean expression evaluates to true
With this knowledge, the logic of the return statement becomes fairly straightforward.
Assume, for example, that day_diff = 5
Then taking the statement from above step-by-step:
return day_diff == 0 && (
diff < 60 && "just now" ||
diff < 120 && "1 minute ago" ||
diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" ||
diff < 7200 && "1 hour ago" ||
diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") ||
day_diff == 1 && "Yesterday" ||
day_diff < 7 && day_diff + " days ago" ||
day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago";
First, day_diff == 0
will evaluate to false
and the right-hand side:
(diff < 60 && "just now" ||
diff < 120 && "1 minute ago" ||
diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" ||
diff < 7200 && "1 hour ago" ||
diff < 86400 && Math.floor( diff / 3600 ) + " hours ago")
...is not evaluated. Both sides of:
day_diff == 1 && "Yesterday"
...evaluate to false
. Next is:
day_diff < 7 && day_diff + " days ago"
In this expression day_diff < 7
evaluates to true
, so its right-hand side, which is a string, will be evaluated and its result returned.
Further reading:
http://www.ejball.com/EdAtWork/2005/02/19/JavaScriptBooleanOperators.aspx
It says right there: return ...
and then goes into a long nested list of basically "inline ifs
". ;-)
In Javascript, the boolean operators return the value of one of the operands, not just true
or false
. E.g. 0 || 'foo'
returns 'foo'
. This characteristic is used in conjunction with operator short-circuiting. false && true
will not evaluate the true
side and return false
immediately, since the whole expression must be false
.
Are you a Java person? Because if so you probably think if(x)
needs x to be a boolean, and that "x && y" returns a boolean. It doesn't in JavaScript and in a lot of other languages like Perl. In many weakly-typed languages && is called the guard operator and || is called the default operator. They return one of their two arguments.
The return statement is just a complicated if/else cascade that ends up returning a string in all the non-error cases.
E.g.
return day_diff == 0 && (
diff < 60 && "just now" ||
diff < 120 && "1 minute ago" || [...]
If day_diff is zero (meaning the date is today), then it falls into the check to see if it's less than 60. If that statement is true, then it will short-circuit evaluating the rest of the whole thing, and return the value of the expression, which will be "just now". If diff isn't less than 60, it will short circuit the sub-expresion, and move on to the diff < 120
check, and so on.
The strings are always "true" in the expressions, and they also become the result of evaluating the expression when that case matches.
This is functional but fairly obfuscated code. Not for teaching purposes. :)
Yeah, it's weird Javascript stuff. String concatenation evaluates to true and the return
statement at the bottom of prettyDate()
takes advantage of this plus short-circuiting in conditionals.
So basically, in the first case, diff < 60 && "just now"
evaluates to the string "just now" if diff
is indeed less than 60 because all the other top-level items in the conditional are OR'd together so the Javascript evaluator doesn't care about them once this first condition holds true. The same goes on down the line.
The last statement of the line has the form
return boolExpression && otherBoolExpression
When javascript reads this, the following happens:
- if
boolExpression
if falsey, then it returnsboolExpression
- otherwise, it returns
otherBoolExpression
.
This is JavaScript's way of doing short-circuit logic.
Since, in this case, otherBoolExpression
uses a string concatenation, the function returns string, as long as dayDiff
is not 0.
Basically you just have to know that
return day_diff == 0 && "lala" + "lolo"
will return lalalolo
if day_diff == 0
... because of operator precedence.
So it's just a shorter way to write
if (day_diff == 0) {
if (diff < 60) {
return "just now"
} else (...)
} else {
if (day_diff == 1) {
return "..."
}
}
It is playing the dangerous game of relying on operator precedence to process conditionals:
+
trumps &&
which trumps ||
See: https://developer.mozilla.org/en/JavaScript/Reference/Operators/Operator_Precedence
This is how I read it:
(day_diff == 0 && (
(diff < 60 && "just now") ||
(diff < 120 && "1 minute ago") ||
(diff < 3600 && Math.floor( diff / 60 ) + " minutes ago") ||
(diff < 7200 && "1 hour ago") ||
(diff < 86400 && Math.floor( diff / 3600 ) + " hours ago")
)) ||
(day_diff == 1 && "Yesterday") ||
(day_diff < 7 && day_diff + " days ago") ||
(day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago");
There are two variables: diff - difference in seconds, daydiff - difference in days. If the daydiff is zero, the return value is based on diff, else it is number of days.
Read the 'return' as series of 'if' / 'else' clauses deciding the return string.
The return value of the prettyDate() function depends on two variables day_diff
and diff
used in the following evaluation
return day_diff == 0 && ( diff < 60 && "just now" || diff < 120 && "1 minute ago" || diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" || diff < 7200 && "1 hour ago" || diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") || day_diff == 1 && "Yesterday" || day_diff < 7 && day_diff + " days ago" || day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago";
That evaluation can be written more typically as nested if/else statements, but no string is returned if the time
argument is a date that was more that 31 days ago.
if (day_diff == 0) {
if (diff <60)
return "just now";
else if (diff < 120)
return "1 minute ago";
else if (diff < 3600)
return Math.floor( diff / 60 ) + " minutes ago";
else if (diff < 7200)
return "1 hour ago";
else if (diff < 86400)
return Math.floor( diff / 3600 ) + " hours ago";
}
else if (day_diff == 1)
return "Yesterday";
else if (day_diff < 7)
return day_diff + " days ago";
else if (day_diff < 31)
return Math.ceil( day_diff / 7 ) + " weeks ago"
(and slightly updated with template literals which emphasize a string cis being returned)
if (day_diff == 0) {
if (diff <60) return "just now";
else if (diff < 120) return "1 minute ago";
else if (diff < 3600) return `${Math.floor( diff / 60 )} minutes ago`;
else if (diff < 7200) return "1 hour ago";
else if (diff < 86400) return `${Math.floor( diff / 3600 )} hours ago`;
}
else if (day_diff == 1) return "Yesterday";
else if (day_diff < 7) return `${day_diff} days ago`;
else if (day_diff < 31) return `${Math.ceil( day_diff / 7 )} weeks ago`;
精彩评论