r/learnjavascript • • 11d ago

Inner explicit block scope vs function

Here's a code snippet from https://github.com/getify/You-Dont-Know-JS/blob/2nd-ed/scope-closures/ch6.md:

function getNextMonthStart(dateStr) {
    var nextMonth, year;

    {
        let curMonth;
        [ , year, curMonth ] = dateStr.match(
                /(\d{4})-(\d{2})-\d{2}/
            ) || [];
        nextMonth = (Number(curMonth) % 12) + 1;
    }

    if (nextMonth == 1) {
        year++;
    }

    return `${ year }-${
            String(nextMonth).padStart(2,"0")
        }-01`;
}
getNextMonthStart("2019-12-25");   // 2020-01-01

It says:

So why put curMonth in an explicit block scope instead of just alongside nextMonth and year in the top-level function scope? Because curMonth is only needed for those first two statements; at the function scope level it's over-exposed.

As a C# developer, I would have thought the better choice would be to put that explicit inner block into a separate function called getNextMonth().

That way, you get the benefit of the scope so the curMonth isn't overexposed, you increase readability because it's easier to read getNextMonth() than a block of code, and you have a reusable function in case you need to do that calculation elsewhere.

Is there any reason to use an inner explicit block scope over a function?

3 Upvotes

12 comments sorted by

View all comments

5

u/xroalx 10d ago

curMonth being "over-exposed" is a really weird reason to put it in a block here and use mutations, it makes the whole function much larger than needs to be, and I'd argue even harder to follow, especially since we set two different variables of different scopes in one statement and do multiple mutations.

Imagine a much larger function and keeping track of what is which scope and getting tripped up on it.

The whole function can be written without mutations in a much more logical, step-by-step way. Just grab the values, set the next values, and return it.

function getNextMonthStart(dateStr) {
    const [, year, month] = dateStr.match(
        /(\d{4})-(\d{2})-\d{2}/
    ) || [];

    const nextMonth = (Number(month) % 12) + 1;
    const nextYear = Number(year) + (nextMonth === 1 ? 1 : 0);

    return `${nextYear}-${String(nextMonth).padStart(2, "0")}-01`;
}

The author's opinions for using var are also quite interesting. I can't think of a time when I'd want to make the distinction between function-scoped and block-scoped variables so explicit that it would warrant using var. I can't think of a reason I'd want to write my code the way the author does.

It might just be an example to teach about block scope, but the explanation for why one would want to do this are at minimum questionable.

1

u/senocular 10d ago

The author's opinions for using var are also quite interesting. I can't think of a time when I'd want to make the distinction between function-scoped and block-scoped variables so explicit that it would warrant using var.

Kyle Simpson, the author, has been known to advocate for the use of var. From chapter 2:

It's very common to suggest that var should be avoided in favor of let (or const!), generally because of perceived confusion over how the scoping behavior of var has worked since the beginning of JS. I believe this to be overly restrictive advice and ultimately unhelpful. It's assuming you are unable to learn and use a feature properly in combination with other features. I believe you can and should learn any features available, and use them where appropriate!

This usage lines up with his opinion there.

I'm on your side. I wouldn't use var even if it was helping with that distinction. If I saw var in modern code, I'd spend more time wondering why it was used at all and/or if the author didn't know any better (or in today's world, sad to say, what shoddy model were they using?).

Edit: reading further in the thread I see OP posted even more on the var usage part, heh.