向page.includeJs()和page.eevaluate()内的匿名函数传递参数



一个小背景。。。我对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:空

好消息是,我知道什么有效,什么无效,但保持一点一致性如何?

为什么includeJsevaluate有区别?

将参数传递给匿名函数的正确方法是什么?

PhantomJS的难点在于有两个执行上下文-Phantom上下文,它是机器的本地上下文,可以访问phantom对象和required模块;远程上下文,它存在于无头浏览器的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本身提供了此功能)。

最新更新