web scraper for emails and links
I choose to focus on the Single Responsibility Principle in this answer.
To move closer to the Single Responsibility Principle, I think some of the functionality needs to be moved around. I have decomposed the class and removed all state that is shared on the class (rhetorical question: is the class responsible for state or functionality? If it is &quot;both&quot;, then surely that violates SRP?!). Shared state like this can get tough to reason about and lead to an ever-larger class as the functionality evolves over time.
How about a scraper object, with one method run that accepts a URL and returns the result for a URL (and its sub-URLs)?
Usage:
import scraper from './scraper.js'

const { emails: { valid, dangerous }, links: { internal } } = await scraper.run('http://www.example.com')

We configure a result object which we then pass around to populate, before it is returned:
const scraper = {
    async run(url) {
        const result = {
            emails: { valid: [], dangerous: [] },
            links: { internal: [] }
        }
        return scrape(url, result)
    }
}

We define a scrape function, responsible for coordinating the scraping of a URL:
async function scrape(url, result, visited=new Set()) {
    if (visited.has(url)) {
        return result
    }

    visited.add(url)
    const dom = await fetchDom(url)
    const internalLinks = findInternalLinks(dom)
    const { valid, dangerous } = validateEmails(findEmails(dom))

    for (let link of internalLinks) {
        await scrape(link, result, visited)
    }

    result.links.internal.push(...internalLinks)
    result.emails.valid.push(...valid)
    result.emails.dangerous.push(...dangerous)

    return result
}

Function fetchDOM is responsible for retrieving the DOM from the network for a URL:
async function fetchDOM(url) {
    try {
        const { status, data } = await axios(url)
        switch (status) {
            case 200:
                return new JSDOM(await data)
            case 404:
                console.log('this page doesnt exists')
                return process.exit(1)
        }
    } catch (error) {
        console.log(error)
        return process.exit(1)
    }
}

findInternalLinks is responsible for returning an array of internal links, found within the supplied DOM:
function findInternalLinks({ window: { document: body } }) {
    let links = body.querySelectorAll('a[href^=&quot;/&quot;]')
    return Array.from(links).map(link=&gt;link.getAttribute('href'))
}

findEmails is responsible for returning the an array of emails, found within the supplied DOM:
const EMAIL_REGEX = /([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+)/gi

function findEmails({ window: { document: { body: { innerHTML: html } } }) {
    return html.toString().match(EMAIL_REGEX)
}

validateEmails splits an array of emails into two sets: valid and dangerous:
const DANGEROUS_DOMAINS = ['loan', 'work', 'biz', 'racing', 'ooo', 'life', 'ltd', 'png']

function validateEmails(emails) {
    const dangerous = []
    const valid = Array.from(emails).filter(email=&gt;{
        let domainName = email.split('@')[1].split('.')[1]
        if (DANGEROUS_DOMAINS.includes(domainName)) {
            dangerous.add(email)
            return false
        } else
            return true
        })
    return { valid, dangerous }
}

The writing of a file is a wholly separate responsibility. Perhaps create an emailFileWriter object to encapsulate it:
const emailFileWriter = {
    write(emails) {
        return new Promise((resolve,reject)=&gt;{
            fs.writeFile('./emails.txt', emails, err=&gt;{
                if (err) {
                    reject('Could not write file')
                }
                resolve('success')
            })
        })
    }
}

I would put each of these functions and objects in a separate file. Each has its own responsibility after all.