Initial solution (~ 15 ms)
Interested by your question, I first looked for a different strategy to make it faster. Then I ended with a much reduced code, which was also more readable... but it ran much lower than yours!
So I went back to the initial version, and followed your thought about skipping some HTML parts. And you're right, it's the right way to make it faster: on my desktop your version took about 20 ms, while the new one tooks only about 15 ms.
To achieve that, I changed what follows:
- drop
class="r-month"
from the <div>
s and changed the .r-month {}
rule into #r-month div{}
- drop
class="r-title"
from the <em>
s and changed the .r-title {}
rule into #r-title em {}
- drop the
</tr>
and </td>
, which are optional with HTML5
I also dropped tags that seem not required here, but maybe you'll want to keep them for accessibility purposes:
<thead>
+ </thead>
and <tbody>
+ </tbody>
<i>
+ </i>
A better solution (~ 15 ms, increased readability)
After reading your comments under this answer I did some additional tests, so I discovered something really important: the larger part of the time spent to achieve the whole work comes from r_dp.innerHTML = ...
affectation.
It happens that my initial alternative strategy used direct implementation in the final HTML code, so using a lot of such affectations.
In other words, while your version schematically looks like:
var out = '';
while (...) {
...
out += ... // n times
...
}
r_dp.innerHTML = out;
the mine worked like this:
while (...) {
...
r_dp.innerHTML += ... // n times
...
}
So once I realized that those multiple affectations had a great impact on the time spent, I simply turned back to a unique final affectation.
The result is that now my version:
- has a reduced code, with increased readability, as already evoked
- and works faster (about 15 ms) without any of the HTML restrictions I suggested above
Here is this version:
var monthsNum = 50, // Number of months to generate
month = 1, // zero-based index
year = 2016,
out = '';
const MONTHS = [
'January', 'February', 'March', 'April', 'May', 'June',
'Juli', 'August', 'September', 'October', 'November', 'December'
];
const WEEK_HEAD =
`${['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
.reduce((row, e) => `${row}<td>${e}</td>`, '')}`;
function _month(begMonth, endMonth, monthYear) {
var grid = '',
weekFirstDay = (begMonth.getDay() + 6) % 7,
monthLastDay = endMonth.getDate();
for (var i = 0; i < 6; i++) {
grid += `<tr>${_week(i * 7, weekFirstDay, monthLastDay, monthYear)}</tr>`;
}
return grid;
}
function _week(daysOffset, weekFirstDay, monthLastDay, monthYear) {
var row = '';
for (var j = daysOffset, n = daysOffset + 6; j <= n; j++) {
var day = j - weekFirstDay + 1;
row += `<td ${
j < weekFirstDay || day > monthLastDay ?
'class="notCurMonth"> '
: `data-date="${day}/${monthYear}"><i>${day}</i>`
}</td>`;
}
return row;
}
// Begin perf measure
var perfStart = performance.now();
for (var i = 0; i < monthsNum; i++) {
var begMonth = new Date(year, month + i, 1),
endMonth = new Date(year, month + i + 1, 0),
curMonth = begMonth.getMonth(),
curYear = begMonth.getFullYear();
out += `
<div class="r_month">
<em class="r_title">
${MONTHS[curMonth]} <span>${curYear}</span>
</em>
<table>
<thead><tr>${WEEK_HEAD}</tr></thead>
<tbody>${_month(begMonth, endMonth, `${curMonth + 1}/${curYear}`)}</tbody>
</table>
</div>`;
}
r_dp.innerHTML = out;
// log performance
var perfEnd = performance.now();
var log = 'getMonth perf @ '+ monthsNum +' months: ' + Math.round((perfEnd-perfStart)*100)/100 + 'ms';
console.log(log);
r_dp.innerHTML = '<h3>' + log + '</h3>' + r_dp.innerHTML;
html { box-sizing: border-box; font-size:62.5%; font-family: arial, helvetica, sans-serif; }
*, *:before, *:after { box-sizing: inherit; }
body { padding:10px 0; background: #fafafa; font-size:1.6rem; color:#333; }
i { font-style:normal; }
h3 { margin: 10px 0; }
#r_dp {
width: 95%;
margin:0 auto;
position:relative;
}
.r_month { width: 280px; float:left; margin-right:20px; margin-bottom:20px; background: #fff; }
table { width:100%; border-collapse:collapse; }
td {
width:auto;
padding: 5px; border:1px solid #ddd;
text-align:center;
}
.r_title {
padding:10px 5px;
text-align:center;
font-size:12px;
display:block;
font-style:normal;
width:100%;
background: #03A9F4;
color:#fff;
text-transform:uppercase;
}
<body id="r_dp"></body>
Little enhancement (~ 13 ms)
If we also apply some of the HTML restrictions proposed in my previous solution (but not the ones you pointed as inconvenient in your comments), we improve performance yet, about 13 ms.
Here is it:
var monthsNum = 50, // Number of months to generate
month = 1, // zero-based index
year = 2016,
out = '';
const MONTHS = [
'January', 'February', 'March', 'April', 'May', 'June',
'Juli', 'August', 'September', 'October', 'November', 'December'
];
const WEEK_HEAD =
`${['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
.reduce((row, e) => `${row}<td>${e}`, '')}`;
function _month(begMonth, endMonth, monthYear) {
var grid = '',
weekFirstDay = (begMonth.getDay() + 6) % 7,
monthLastDay = endMonth.getDate();
for (var i = 0; i < 6; i++) {
grid += `<tr>${_week(i * 7, weekFirstDay, monthLastDay, monthYear)}`;
}
return grid;
}
function _week(daysOffset, weekFirstDay, monthLastDay, monthYear) {
var row = '';
for (var j = daysOffset, n = daysOffset + 6; j <= n; j++) {
var day = j - weekFirstDay + 1;
row += `<td ${
j < weekFirstDay || day > monthLastDay ?
'class="notCurMonth"> '
: `data-date="${day}/${monthYear}"><i>${day}</i>`
}`;
}
return row;
}
// Begin perf measure
var perfStart = performance.now();
for (var i = 0; i < monthsNum; i++) {
var begMonth = new Date(year, month + i, 1),
endMonth = new Date(year, month + i + 1, 0),
curMonth = begMonth.getMonth(),
curYear = begMonth.getFullYear();
out += `
<div>
<em>
${MONTHS[curMonth]} ${curYear}
</em>
<table>
<thead><tr>${WEEK_HEAD}</thead>
<tbody>${_month(begMonth, endMonth, `${curMonth + 1}/${curYear}`)}</tbody>
</table>
</div>`;
}
r_dp.innerHTML = out;
// log performance
var perfEnd = performance.now();
var log = 'getMonth perf @ '+ monthsNum +' months: ' + Math.round((perfEnd-perfStart)*100)/100 + 'ms';
console.log(log);
r_dp.innerHTML = '<h3>' + log + '</h3>' + r_dp.innerHTML;
html {
box-sizing: border-box;
font-size:62.5%;
font-family: arial,
helvetica, sans-serif;
}
*, *:before, *:after {
box-sizing: inherit;
}
body {
padding:10px 0;
background: #fafafa;
font-size:1.6rem;
color:#333;
}
i {
font-style:normal;
}
h3 {
margin: 10px 0;
}
#r_dp {
width: 95%;
margin:0 auto;
position:relative;
}
#r_dp div {
width: 280px;
float:left;
margin-right:20px;
margin-bottom:20px;
background: #fff;
}
table {
width:100%;
border-collapse:collapse;
}
td {
width:auto;
padding: 5px;
border:1px solid #ddd;
text-align:center;
}
#r_dp em {
padding:10px 5px;
text-align:center;
font-size:12px;
display:block;
font-style:normal;
width:100%;
background: #03A9F4;
color:#fff;
text-transform:uppercase;
}
<body id="r_dp"></body>
More enhancement (~ 12 ms, more increased readability)
Going ahead from this, I had an idea for a possible yet more improved version, where a whole HTML skeleton is previously buit, then used for each month.
This way, it's a little faster and code is yet more reduced and readable.
Here is it:
var monthsNum = 50, // Number of months to generate
month = 1, // zero-based index
year = 2016,
out = '';
const
MONTHS = [
'January', 'February', 'March', 'April', 'May', 'June',
'Juli', 'August', 'September', 'October', 'November', 'December'
],
WEEK_HEAD =
`${['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
.reduce((row, e) => `${row}<td>${e}`, '')}`,
GRID_SKELETON = `
<div>
<em>@title</em>
<table>
<thead><tr>@head</thead>
<tbody>${`<tr>${'<td @'.repeat(7)}`.repeat(6)}</tbody>
</table>
</div>`,
EMPTY_CELL = 'class="notCurMonth"> ';
// Begin perf measure
var perfStart = performance.now();
for (var i = 0; i < monthsNum; i++) {
var begMonthDate = new Date(year, month + i, 1),
curMonth = begMonthDate.getMonth(),
curYear = begMonthDate.getFullYear(),
weekFirstDay = (begMonthDate.getDay() + 6) % 7,
endMonthDay = new Date(year, month + i + 1, 0).getDate(),
cell = 0,
day = 0;
out += GRID_SKELETON
.replace(/@title/, `${MONTHS[curMonth]} ${curYear}`)
.replace(/@head/, WEEK_HEAD)
.replace(/(@)/g, () => (
(cell++ < weekFirstDay || day >= endMonthDay) ? EMPTY_CELL
: `data-date="${++day}/${curMonth + 1}/${curYear}">${day}`
))
}
// output out-array to #dp
r_dp.innerHTML = out;
// log performance
var perfEnd = performance.now();
var log = 'getMonth perf @ '+ monthsNum +' months: ' + Math.round((perfEnd-perfStart)*100)/100 + 'ms';
console.log(log);
r_dp.innerHTML = '<h3>' + log + '</h3>' + r_dp.innerHTML;
html {
box-sizing: border-box;
font-size:62.5%;
font-family: arial,
helvetica, sans-serif;
}
*, *:before, *:after {
box-sizing: inherit;
}
body {
padding:10px 0;
background: #fafafa;
font-size:1.6rem;
color:#333;
}
i {
font-style:normal;
}
h3 {
margin: 10px 0;
}
#r_dp {
width: 95%;
margin:0 auto;
position:relative;
}
#r_dp div {
width: 280px;
float:left;
margin-right:20px;
margin-bottom:20px;
background: #fff;
}
table {
width:100%;
border-collapse:collapse;
}
td {
width:auto;
padding: 5px;
border:1px solid #ddd;
text-align:center;
}
#r_dp em {
padding:10px 5px;
text-align:center;
font-size:12px;
display:block;
font-style:normal;
width:100%;
background: #03A9F4;
color:#fff;
text-transform:uppercase;
}
<body id="r_dp"></body>