有人能解释一下关于字符串拼接的这种奇怪的JS行为吗?

10

我刚刚在gist上发布了这个:https://gist.github.com/2228570

var out = '';

function doWhat(){
    out += '<li>';
    console.log(out === '<li>'); // at this point, out will equal '<li>'
    return '';
}

out += doWhat();
console.log(out, out === '<li>');
// I expect out to == '<li>', but it's actually an empty string!?

这种行为很奇怪,有人能解释一下吗?在谷歌上很难找到答案。使用out +=out = out +都没有区别。
编辑:@paislee创建了一个JSFiddle演示,如果将doWhat放在单独的一行,它会按预期行事:http://jsfiddle.net/paislee/Y4WE8/

稍微编辑了我的答案,以提供解释和实现所需功能的解决方案(在函数内添加字符串到“out”并将返回值添加到“out”中)。只需要交换“out”和“doWhat()”(使用“out = doWhat() + out”而不是“out = out + doWhat()”)。 - 0b10011
4个回答

8

看起来你期望在+=被评估之前调用doWhat

但是,代码行的执行顺序是:

out += doWhat();      // original line
out = out + doWhat(); // expand `+=`
out = '' + doWhat();  // evaluate `out`, which is currently an empty string
out = '' + '';        // call `doWhat`, which returns another empty string
out = '';             // result
< p > doWhat 内的 out += '<li>'; 只是更新变量,但太晚了,没有持久影响。


我将此标记为解决方案,因为它最清晰地(对我来说)解释了行为,从而回答了原始问题。有关解决方案,请参见https://dev59.com/Imkw5IYBdhLWcg3waZ1W#9913555和https://dev59.com/Imkw5IYBdhLWcg3waZ1W#9913382。 - Andrew

6
混淆的原因在于您期望 doWhat() 直接修改 out。显然,在调用此函数之前,将检索要追加的值。

这是逻辑:

  1. 获取 out 的值为 ''
  2. 调用 doWhat() 并将结果追加到第一个值中('' + '' = '')
  3. 将结果分配给 out ('')

以这种方式混合返回值和直接修改只会带来问题,正如您的示例所展示的那样。也许您应该尝试返回 <li>


4
我认为混淆的原因在于他在doWhat函数里面赋值给了out变量。(实际上,内部的out变量覆盖了外部的那个,它们并不是同一个变量。) - Ismail Badawi
3
+= 不等于 = … 这是完全不同的。 - sitifensys
2
@isbadawi 在 doWhat 函数内部没有使用 var out,所以我没有看到任何遮蔽。或者我是瞎了吗? - sitifensys
1
@isbadawi,实际上,它同一个变量。在JavaScript中,默认情况下变量是全局的(而不是相反)。因此,除非您在函数内部明确在out前面放置var,否则它将继续向上,直到找到var out或创建一个名为out的新全局变量。 - 0b10011
1
@Andrew,你可以这样想象:var out = '';function doWhat(){out = '<li>';return '';}out = ''+doWhat(); 其实等同于 out = ''+''; (out += doWhat(); == out = out + doWhat(); == out = '' + doWhat; 因为在调用函数之前,会先获取变量out的值。)此外,请注意,你在第一个评论中提到的操作可以通过简单地调用doWhat()来实现,无需将doWhat()的返回值赋给out - 0b10011
显示剩余5条评论

2

设想一下:

// out is undefined
var out = '';
// out is ''
function doWhat(){
 out += '<li>';
 // out is '<li>';
 return '';
}
out = out + doWhat();
// out += doWhat(); is the same as:
// out = out + doWhat(); is the same as :
// out = '' + doWhat; because the value of `out` is read when `out` is first found
// out is ''

并且进行线性化:

// out is undefined
var out = '';
// out is ''
out += '<li>';
// out is '<li>';
out = '' + ''; // first '' is value of `out` before function is called, second is what the function returns
// out is ''

解决方案

var out = '';
function doWhat(){
 out += '<li>';
 return '';
}
out = doWhat() + out; // Note `out` is *after* `doWhat()`

简而言之

目前,你的代码执行结果与以下代码相同:

out = out + doWhat();

在调用doWhat()之前,读取out的值。为了使其按照您的期望工作,请简单地反转顺序:

out = doWhat() + out;

这将按您的预期读取doWhat()之后out的值。

哇,我从来没有想到过这个解决方案!绝对比我原本要做的简单... - Andrew
@Andrew,很高兴它对你有用 :) 你可以接受这个答案来标记问题已解决吗?(请参阅FAQ中的如何提问。) - 0b10011
我会标记其中一个为已解决,你和@cmw都提供了很好的解决方案。 - Andrew
@Andrew,我在结尾处添加了一个简化的解释,更详细地解释了解决方案中的注释 :) - 0b10011

1
正如Jonathan所说,您从函数中返回了一个空字符串,因此即使您在doWhat内部通过将'
  • '附加到out来修改全局变量,javascript也会在您进行函数调用时将函数返回值附加到out的值上。
    您也可以这样做:
    var out = '';
    
    function doWhat(){
        out += '<li>';
        return true;
    }
    
    doWhat();
    

    你需要在函数内部和返回值后面同时添加字符串的特殊原因吗?

    [编辑]

    看了你在这个回答的评论中发布的实际示例,我觉得你可能正在尝试有条件地返回一个附加到 out 的额外值从 doWhat。如果我错了,请纠正我。您的代码如下:

    var out = '', temp;
    
    function doWhat(){
       out += '<li>';
    }
    
    out += typeof (temp = doWhat()) === 'undefined' ? '' : temp;
    

    从这个演示文稿中可以看出,变量temp的值始终为未定义,因为函数没有返回任何可以被键入的内容。如果您计划使函数有时返回一个值,然后再将其附加到其他内容中,您可以通过在结尾处将其拆分为两行来实现您想要的效果:
    var out = '', temp;
    function doWhat(){
        out += '<li>';
    }
    temp = doWhat();
    out += typeof (temp === undefined) ? '' : temp;
    

    这有点尴尬,我可能会尝试将两个附加操作都移动到函数内部。

    var out = '';
    function doWhat () {
       out += '<li>';
       if (some condition is met) { out += 'some extra string'; }
    }
    doWhat();
    

  • 我只是添加了返回值以简化示例。真正的示例:https://gist.github.com/2228570/48977b42d11ec7fd06e8cff7f3375e47a03ca4d8 所有这些的原因是为了一个名为Vash的模板库:http://github.com/kirbysayshi/vash - Andrew
    你为什么要评估函数调用的 typeof?如果它没有返回值,它将始终是未定义的。你的真实函数是否具有条件返回值? - cmw
    这是因为在这种情况下,函数调用可能会返回一个值或者不返回。如果它返回一个值,那么应该将其添加到 out 中,因为整个过程有点像响应流。typeof 是为了查看函数是否返回任何内容,如果没有,则用空字符串替换其输出以保持整个连接有效。 - Andrew
    @cmw:你提到了“闭包”。我不认为这是一个闭包。(我错了吗?)这只是一个引用全局变量的函数,OP得到的输出结果是执行顺序的结果。 - MrWhite
    Andrew,这正是我怀疑的。我已经相应地更新了我的答案。@w3d,你是对的,我的错误。我已经更正了我的答案术语。 - cmw
    你的更新答案太棒了,你使用临时变量的解决方案正是我在发布这个问题之前已经决定采用的! - Andrew

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接