如何正确将Puppeteer Page传递至load/domcontentloaded事件?
问题解答
一、domcontentloaded事件回调中获取Page实例的可行性与陷阱
1. 你写的(this) => {}写法不可行
Puppeteer的domcontentloaded事件不会传递任何参数(文档标注Never就是这个意思),回调里的this参数根本拿不到Page实例;再加上箭头函数的this继承自外层作用域,完全不是你期望的Page对象,这种写法直接无效。
2. 正确获取Page实例的两种方式
- 方式一:利用外层块级作用域变量
用let或const声明page(绝对不能用var),块级作用域会保证每次循环的page都是独立绑定,回调里能正确引用对应的Page实例:// 循环中用let/const,避免变量共享问题 for (/* 循环条件 */) { const page = await browser.newPage(); page.on('domcontentloaded', async () => { await page.evaluate(() => { console.log('do something'); }); }); // 后续操作page } - 方式二:用普通函数回调的this指向
不要用箭头函数,普通函数的this会自动指向触发事件的Page实例:const page = await browser.newPage(); page.on('domcontentloaded', async function() { await this.evaluate(() => { console.log('do something'); }); });
3. 用var声明page的致命问题
如果循环里用var声明page,因为var是函数作用域,所有循环迭代的回调会共享同一个page变量。当循环快速执行时,后续创建的Page会覆盖page的值,前面的domcontentloaded回调触发时,操作的已经是最新的Page实例,必然出现异常。必须用let/const解决这个问题。
二、domcontentloaded后快速执行JS的最优方式
你写的代码已经是很合理的实现方式,具体分析:
page.goto(url, {waitUntil: 'domcontentloaded'})会在domcontentloaded事件触发后才完成Promise,之后立即执行page.evaluate(),确实能保证JS在domcontentloaded后尽快执行。- 后续的
page.waitForNetworkIdle是可选的:如果你的需求是执行完JS后等待页面网络请求全部完成(比如要获取加载后的完整页面内容),这个步骤是必要的;如果只是要在domcontentloaded后执行JS,不需要等网络空闲,可以去掉这一步。
有没有更优的写法?其实和当前写法效果一致的另一种方式是分开写goto和waitForNavigation,但没必要,当前写法更简洁:
const page = await browser.newPage(); await page.goto(url); await page.waitForNavigation({waitUntil: 'domcontentloaded'}); await page.evaluate(() => { console.log('do something'); });
但这种写法和你原来的写法没有性能差异,因为goto里设置waitUntil本质就是内部调用了waitForNavigation。
总结:你当前的写法已经是domcontentloaded后快速执行JS的最优实现之一,完全满足需求。
内容的提问来源于stack exchange,提问作者Steve Payne
相关产品推荐
相关产品推荐

