Javascript Set,传递给函数时引用中断



tl;dr:当我将Set传递给子函数时,子函数又将新值添加到Set中,新值不会添加到原始Set中。我找到这个wierd,因为它Set是一个对象。为什么会这样?

我正在Node中构建一个网络爬虫,我将使用它访问域上的所有页面。基本算法如下:

  1. 访问页面
  2. 将url保存在包含所有访问过的链接的集合中
  3. 提取所有尚未访问的链接
  4. 对所有新链接重复1-3

我使用Set作为链接的容器,因为查找复杂性为O(1(。我在开始访问链接之前初始化Set,并将其传递到包含逻辑的函数中。问题是,当我将链接添加到Set时,下次调用函数时它就不在了。当我将Set传递给一个函数时,似乎会创建一个新对象。我该如何解决这个问题?

async function initialize() {
const visitedLinks = new Set();
await scrapePage(ROOT_URL, visitedLinks);
}
async function scrapePage(url, visitedLinks) {
let body = (await axios.get(url)).data;
// Here I add the url to visitedLinks
visitedLinks.add(url);

const links = getLinks(body);
const linksNotVisited = getLinksNotVisited(
links,
visitedLinks
);
for (let i = 0; i < linksNotVisited.length; i++) {
await scrapePage(linksNotVisited[i], visitedLinks);
}
}

问题是,当我将链接添加到Set时,下次调用函数时它不在那里。

不,这不是问题所在。Set运行良好,不会改变其身份。

问题是,在递归函数中,您在错误的时间向集合添加链接。通过只在实际访问时添加url,当多个页面指向同一个url时,该url可能会成为多个linksNotVisited数组的一部分(在不同的递归调用中(,然后会被多次访问。

相反,在加载该页面之前,请检查链接是否已被访问:

async function scrapePage(url, visitedLinks) {
if (visitedLinks.has(url)) return;
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
let body = (await axios.get(url)).data;
visitedLinks.add(url);

for (const link of getLinks(body)) {
// the if statement would work if put here as well,
// but it's cleaner as a base case of the recursion
await scrapePage(link, visitedLinks);
}
}

或者保留一组linksNoLongerToBeVisited,当将链接添加到随后迭代的数组时立即填充:

async function scrapePage(url, linksNoLongerToBeVisited) {
let body = (await axios.get(url)).data;
const links = getLinks(body);
const linksNotVisited = links.filter(link => {
if (linksNoLongerToBeVisited.has(link)) return false;
linksNoLongerToBeVisited.add(link);
//  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
return true;
});
for (const link of links) {
await scrapePage(link, linksNoLongerToBeVisited);
}
}

Btw,如果您不需要递归结构,那么使用Set作为队列,以迭代方式编写此函数可能更容易:

async function initialize() {
const visits = new Set([ROOT_URL]);
for (const url of visits) {
const body = (await axios.get(url)).data;
for (const link of getLinks(body)) {
visits.add(link);
}
}
}

最新更新