具有单个字符串的数组会自动且意外地转换为字符串

Array with a single String getting automatically and undesireably converted to just a String

在我的 CasperJS 脚本中,当我通过评估函数将一个数组传递给我的函数时,它可能有 1 个或多个字符串。当有多个字符串时,它按预期工作,但当数组只有 1 个字符串时,它的行为非常奇怪。如果我传递一个里面只有一个字符串的数组,它就变成了字符串。我哪里错了?

我的 CasperJS 脚本:

function myFunction(input) {
  console.log(JSON.stringify(input));
}

//allow console logs through for debugging
casper.on('remote.message', function(message) {
  this.echo(message);
});

//get my page
casper.start(...);

//attempt to call myFunction with an array as input
casper.then(function() {
  var input = ['#someId'];
  this.evaluate(myFunction, input); 
});

期望的输出:

["#someId"]

实际输出:

"#someId"

输出 if var input = ['#firstId', '#secondId']

["#firstId", "#secondId"]

当您使用 JSON.stringify(input) 时,此方法将 JavaScript 值转换为 JSON 字符串,而您发送的是我认为不正确的单个字符串 json格式。

JSON.stringify() 将值转换为表示它的 JSON 表示法:

不保证非数组对象的属性按任何特定顺序进行字符串化。不要依赖字符串化中同一对象内的属性排序。 Boolean、Number 和 String 对象在字符串化过程中被转换为相应的原始值,符合传统的转换语义。 如果在转换过程中遇到未定义、函数或符号,则将其忽略(当在对象中找到时)或截断为 null(当在数组中找到时)。 所有以符号为键的属性都将被完全忽略,即使在使用替换函数时也是如此。 不可枚举的属性将被忽略

请查看 JSON API 及其相关方法,

https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify

这正是 CasperJS 在恰好有两个参数时添加的内容:函数和函数参数。如果它是根据 this:

的数组或对象,它将尝试 "unpack" 您传递的一个参数
Casper.prototype.evaluate = function evaluate(fn, context) {
    ...
    // function context
    if (arguments.length === 1) {
        return utils.clone(this.page.evaluate(fn));
    } else <strong>if (arguments.length === 2) {
        // check for closure signature if it matches context
        if (utils.isObject(context) && eval(fn).length === Object.keys(context).length) {
            context = utils.objectValues(context);
        } else {
            context = [context];
        }
    }</strong> else {
        // phantomjs-style signature
        context = [].slice.call(arguments, 1);
    }
    return utils.clone(this.page.evaluate.apply(this.page, [fn].concat(context)));
};

这会导致有趣的行为,如以下完整脚本所示:

var casper = require('casper').create();

casper.on("remote.message", function(msg) {
    this.echo("Console: " + msg);
});

casper.start('http://example.com/').then(function(){
    var arr = ['#someId'];
    var arrm = ['#someId', '#someOtherId'];
    var obj = {a:'#someId'};
    var objm = {a:'#someId', b:'#someOtherId'};

    this.echo("1");
    this.evaluate(function(arr) {
        console.log(JSON.stringify(arr));
    }, arr);

    this.echo("2");
    this.evaluate(function(arr) {
        console.log(JSON.stringify(arr));
    }, arrm);

    this.echo("3");
    this.evaluate(function(obj) {
        console.log(JSON.stringify(obj));
    }, obj);

    this.echo("4");
    this.evaluate(function(obj) {
        console.log(JSON.stringify(obj));
    }, objm);

    this.echo("5");
    this.evaluate(function(arr, obj) {
        console.log(JSON.stringify(arr));
        console.log(JSON.stringify(obj));
    }, arr, obj);

    this.echo("6");
    this.evaluate(function(a) {
        console.log(JSON.stringify(a));
    }, obj);

    this.echo("7");
    this.evaluate(function(b) {
        console.log(JSON.stringify(b));
    }, objm);

    this.echo("8");
    this.evaluate(function(a, b) {
        console.log(JSON.stringify(a));
        console.log(JSON.stringify(b));
    }, objm);

    this.echo("9");
    this.evaluate(function(b, a) {
        console.log(JSON.stringify(a));
        console.log(JSON.stringify(b));
    }, objm);
}).run();

输出:

1
Console: "#someId"
2
Console: ["#someId","#someOtherId"]
3
Console: "#someId"
4
Console: {"a":"#someId","b":"#someOtherId"}
5
Console: ["#someId"]
Console: {"a":"#someId"}
6
Console: "#someId"
7
Console: {"a":"#someId","b":"#someOtherId"}
8
Console: "#someId"
Console: "#someOtherId"
9
Console: "#someOtherId"
Console: "#someId"

请注意,如果对象的键匹配(请参阅测试 8 和 9),对象将按名称解压缩到 evaluate() 回调参数。

PhantomJS 本身不进行任何类型的解包。这就是 CasperJS。