I solved Mini Linter, but I'm not happy with it

After what seems like an eternity (but was probably only a few hours :laughing: ), I’ve completed the Mini Linter project. I’m fairly happy with what I learned, but I feel like there are some things that could have been better.

In particular, Step 4, the oft-mentioned Overused Words step, gave me a lot of trouble, I think in part because I wanted to solve this in a better way than the suggestions. Manually looping through each word just seems so … manual! I ended up using some code I found and co-opted to make it my own, and it logs the overused words when I call it in a console.log() by itself—but when I concatenate or use a string literal, it gives me the code [object Object] instead.

I want to understand what’s happening here. Do you see why I’m not getting the expected result?

const overusedWordsCount = {};
overusedWords.forEach(word => { 
  overusedWordsCount[word] = storyWords.filter(i => i == word).length; 
})

console.log(`Overused words: ${overusedWordsCount}`); // Overused words: [object Object]
console.log(overusedWordsCount); // { really: 2, very: 5, basically: 1 }

Thanks so much for any input you can offer!

Hello :slight_smile:

I understand your concern and I will be happy to explain why your code resulted in this outcome but I want to warn you that this might be a bit more complex than what you are currently learning.

const overusedWordsCount = {};

In this line, you defined a new const overusedWordsCount and initialized it as an empty object.

In the next lines, you changed the value of this object by adding to it new properties and specifying values of these properties. All correct.

console.log(`Overused words: ${overusedWordsCount}`);

And this is where it gets a little more complicating. You used template literal to interpolate the value of the overusedWordsCount object as a string into another string.

The problem is that objects can represent anything you want. It might be a person, it might be current weather, etc. So JavaScript does not always know how to cast any object to a string. Hold that thought.

console.log(overusedWordsCount);

In this line, it’s clear what you want to achieve. You want to print out the value of the overusedWordsCount. Nothing more, nothing less. It’s clear for us and it’s clear for the computer.


Now take a look at this code:

console.log("a" + "b" + "c"); // results in abc being printed out to the console
console.log({letter: 'a'} + 'b' + 'c'); // results in [object Object]bc beign printed out to the console

Again, the computer does not know what we mean when we try to use object {letter: 'a'} as a string. We might expect a, or maybe we expect letter: a, who knows?

So the computer does not guess, it simply provides the only value that it is sure of - type. So if you don’t specify how JavaScript should cast your object to a string - the casting will result in [object Object],


How to fix this? By providing instructions on how to represent the object as a text value using .toString() method. Currently, you are working with a single object, so this might not make a lot of sense. But later you will be using classes to define templates for objects (every person has a first name and last name). For example:

function Person(firstName, lastName, age) {
   this.firstName = firstName;
   this.lastName = lastName;
   this.age = age;
}

let me = new Person("Maciej", "Wiercioch", 27);

console.log(`Hello, I am ${me}`); // results in "Hello, I am [object Object]"

// method that will be used to provide instructions how to cast object to text value
Person.prototype.toString = function() {
    return `${this.firstName} ${this.lastName}`;
}

console.log(`Hello, I am ${me}`); // results in "Hello, I am Maciej Wiercioch"

So you might do something like this:

const overusedWordsCount = {};
overusedWords.forEach(word => { 
  overusedWordsCount[word] = storyWords.filter(i => i == word).length; 
})

overusedWordsCount.toString = function() {
    let result = '';
    for (var property in this) {
        if(property !== 'toString') {
            result += "\n\t" + property + ", number of occurrences: " + this[property];
        }
    }
    result + "\n";
    return result;
}

console.log(`Overused words: ${overusedWordsCount}`);

// This results in:
// Overused words: 
//     really, number of occurrences: 2
//     very, number of occurrences: 5
//     basically, number of occurrences: 1

Summary:

  • computer always needs specific instructions on how to achieve something;
  • your problem was caused by casting object to the string (text value);
  • you can provide instructions for the computer how to cast given object to the text value by providing toString method.
3 Likes

Wow, fantastic explanation @factoradic. I really appreciate you taking the time to explain this to me. Even though it’s advanced beyond my current lesson, you explained it so well that I was still able to follow it. The lesson I am about to start is on Objects, so I think this experience will be really helpful.

It’s interesting that JavaScript doesn’t throw an error in this case – but instead it returns this [object Object] message. Why not an error?

2 Likes

You are too kind, I’m glad you found this helpful :slight_smile:

Wow, exceptionally good question. I don’t know an answer to this question but there is a very good probable answer. First things first, I need to sketch out a bit of context.

  1. JavaScript history is rather stormy. I will not go into detail (Wikipedia article and other sources already covered this topic in breadth, I recommend watching this talk by Brendan Eich -> https://www.youtube.com/watch?v=GxouWy-ZE80) but you have to know that the first version of JavaScript was created in 10 days. This is a rather short time for creating a programming language.

  2. In many programming languages, there are mechanisms that allow us, programmers to define how the runtime errors should be handled. So with proper error handling, we can create plan b for our application, we can somehow make sure that given error will be handled gracefully.

And with this background, we are ready for the theory. JavaScript standardized version is known as ECMA Script, ECMA is a standards organization. The first ES standard was published in June 1997, almost one and a half year after JS was created. You can read the first standard here. What is interesting is description of the errors. Errors in the first standard were described in the last section that is so short that I can freely quote it here in the full form:

16. Errors
This specification specifies the last possible moment an error occurs. A given implementation may generate errors sooner (e.g. at compile-time). Doing so may cause differences in behavior among implementations. Notably, if runtime errors become catchable in future versions, a given error would not be catchable if an implementation generates the error at compile-time rather than runtime.
An ECMAScript compiler should detect errors at compile time in all code presented to it, even code that detailed analysis might prove to be “dead” (never executed). A programmer should not rely on the trick of placing code within an if (false) statement, for example, to try to suppress compile-time error detection.
In general, if a compiler can prove that a construct cannot execute without error under any circumstances, then it may issue a compile-time error even though the construct might neverbe executed at all.

“If runtime errors become catchable in future versions” implies that runtime errors were not catchable. So there was no way to handle errors - they had to result in the crash of the application. So that’s probably why JavaScript ships default toString method instead of throwing an error.

2 Likes