一个小背景。。。我对javascript和phantom.js有点陌生,所以我不知道这是javascript还是phantom.jsp bug(功能?)。
以下操作成功完成(很抱歉缺少phantom.exit(),完成后只需ctrl+c即可):
var page = require('webpage').create();
var comment = "Hello World";
page.viewportSize = { width: 800, height: 600 };
page.open("http://www.google.com", function (status) {
if (status !== 'success') {
console.log('Unable to load the address!');
phantom.exit();
} else {
page.includeJs('http://code.jquery.com/jquery-latest.min.js', function() {
console.log("1: ", comment);
}, comment);
var foo = page.evaluate(function() {
return arguments[0];
}, comment);
console.log("2: ", foo);
}
});
这项工作:
page.includeJs('http://code.jquery.com/jquery-latest.min.js', function() {
console.log("1: ", comment);
}, comment);
输出:1: Hello World
但不是:
page.includeJs('http://code.jquery.com/jquery-latest.min.js', function(c) {
console.log("1: ", c);
}, comment);
输出:1: http://code.jquery.com/jquery-latest.min.js
而不是:
page.includeJs('http://code.jquery.com/jquery-latest.min.js', function() {
console.log("1: ", arguments[0]);
}, comment);
输出:1: http://code.jquery.com/jquery-latest.min.js
看看第二件作品,这个作品:
var foo = page.evaluate(function() {
return arguments[0];
}, comment);
console.log("2: ", foo);
输出:2: Hello World
这个:
var foo = page.evaluate(function(c) {
return c;
}, comment);
console.log("2: ", foo);
输出:2: Hello World
但不是这个:
var foo = page.evaluate(function() {
return comment;
}, comment);
console.log("2: ", foo);
输出:
ReferenceError:找不到变量:comment
phantomjs://webpage.evaluate():2
phantomjs://webpage.evaluate():3
phantomjs://webpage.evaluate():3
2:空
好消息是,我知道什么有效,什么无效,但保持一点一致性如何?
为什么includeJs
和evaluate
有区别?
将参数传递给匿名函数的正确方法是什么?
PhantomJS的难点在于有两个执行上下文-Phantom上下文,它是机器的本地上下文,可以访问phantom
对象和require
d模块;远程上下文,它存在于无头浏览器的window
中,只能访问通过page.load
加载的网页中加载的内容。
您编写的大部分脚本都是在Phantom上下文中执行的。主要的例外是page.evaluate(function() { ... })
中的任何内容。这里的...
是在沙盒的远程上下文中执行的,不访问本地上下文中的变量和对象。您可以通过以下方式在两个上下文之间移动数据:
- 从传递给
page.evaluate()
的函数返回一个值,或 - 将参数传入该函数
这样传递的值本质上是在每个方向上序列化的——你不能用方法传递复杂的对象,只能传递像字符串或数组这样的数据对象(我不知道确切的实现,但经验法则似乎是,你可以用JSON序列化的任何东西都可以在任何方向上传递)。您不能像使用标准Javascript那样访问page.evaluate()
函数之外的变量,只能访问作为参数显式传递的变量。
那么,你的问题是:为什么includeJs和evaluate之间有区别
-
.includeJs(url, callback)
接受一个回调函数,该函数在Phantom上下文中执行,显然接收url作为其第一个参数。除了参数之外,它还可以访问(像任何普通的JavaScript函数一样)其封闭范围内的所有变量,包括示例中的comment
。它不会在回调函数后获取额外的参数列表——当您在回调中引用comment
时,您引用的是外部变量,而不是函数参数。var foo = "stuff"; page.includeJs('http://code.jquery.com/jquery-latest.min.js', function() { // this callback function executes in the Phantom context console.log("jQuery is loaded in the remote context."); // it has access to outer-scope variables, including "phantom" nowDoMoreStuff(foo, page); });
-
.evaluate(function, args*)
执行一个函数,并将零个或多个参数传递给它(以某种序列化形式)。你需要在函数签名中命名参数,例如function(a,b,c)
,或者使用arguments
对象来访问它们——它们不会自动与你传入的变量同名。var foo = "stuff"; var bar = "stuff for the remote page"; var result = page.evaluate(function(bar2) { // this function executes in the remote context // it has access to the DOM, remote libraries, and args you pass in $('title').html(bar2); // but not to outer-scope vars return typeof foo + " " + typeof bar; }, bar); console.log(result); // "undefined undefined"
因此,对于这些不同方法中的函数,传递参数的正确方式是不同的。对于injectJs
,回调将使用一组新的参数(至少包括URL)进行调用,因此您想要访问的任何变量都需要在回调的封闭范围内(即,您可以在函数的闭包中访问它们)。对于evaluate
,只有一种方法可以传入参数,那就是将它们包含在传递给evaluate
本身的参数中(也有其他方法,但它们很棘手,不值得讨论,因为PhantomJS本身提供了此功能)。