为什么插入 document.write 的脚本可以更改变量

Why can scripts inserted with document.write change variables

本文关键字:改变 变量 脚本 插入 document write 为什么      更新时间:2023-09-26

为什么下面的脚本打印 33 而不是 31?

<script>
var i = 1;
document.write("<script> i=3; document.write(i); </scr" + "ipt>");
document.write(i);
</script>

这个打印了31,有什么区别?

<script>
var i = 1;
document.write("<script> i=3; document.write(i); </scr" + "ipt>" + i);
</script>

这是它的工作方式顺序:

  1. 定义一个名为 i 的变量,其值为 1
  2. 将新脚本写入文档

    2.1. 这个新脚本将i重新定义为3

    2.2. 脚本将i写入文档(请记住,现在3)。

  3. 您再次将i写入文档(值与重新定义它时的值仍然相同,3)到文档中。

  4. 最终结果是33,因为你写了两次3


这是第二个块的顺序:

  1. 定义一个变量i,值为 1
  2. JavaScript 连接字符串和i,创建"<script> i=3; document.write(i); </script>1"(注意末尾的1,这是为了做连接)
  3. JavaScript 将新的串联字符串写入文档。

    3.1. i被重新定义为3

    3.2. document.writei(之前已重新定义为3)。

  4. 由于串联和document.write,您最终会得到"3<script> i=3; document.write(i); </script>1"作为最终文档。 显然,<script>的内容是不可见的。 所以你最终会得到31.

为了使第一个示例的输出为 31,在解析器对该输出执行任何操作之前,最外层的脚本块必须一直运行并生成其所有输出。这是一种完全合理的方式来认为它会起作用(我认为它多年来一直如此);这是不正确的。:-)浏览器比这更主动。

浏览器的解析器和 JavaScript 引擎必须协同工作,以处理不使用 asyncdefer 属性的脚本标记。关于这一点的两个关键点:

  1. 当解析器看到一个完整的script块时,它会尖叫停止并将其交给 JavaScript 引擎立即运行。

  2. 调用document.write向解析器提供新令牌;如果解析器看到完整的令牌,它将在调用document.write期间处理它。如果该完整令牌是脚本块,则"处理它"涉及在当前调用 JavaScript 引擎期间再次调用它。您可以将每个document.write调用视为对解析器的调用,将每个完整的脚本块视为对 JavaScript 引擎的调用。与函数调用一样,这些可以嵌套。

第二点是为什么你看到的是33而不是31。您编写document.write脚本是在调用 document.write 期间执行的,而不是之后执行的,因此i在该document.write完成之前更新为 3(和输出)。因此,当您到达主脚本块末尾的document.write时,i 已经更改为 3 ,并且已经输出了一次。

在第二个示例中,您在调用document.write之前使用i(当您将其附加到文本时),因此您当然会看到i的值,就像它一样,1

这可能会使调用顺序更清晰:

<script>
document.write("<script>document.write('a');</scr" + "ipt>");
document.write('b');
</script>

结果是ab,而不是ba。第一个document.write的代码输出在最后一个document.write运行之前运行,因此中间document.write的输出显示在最后一个输出之前。

>在第一种情况下注入document.write在第一种情况下重新定义了变量 i,因此第二种document.write也打印了 3。
在第二种情况下,您只有一个 document.write,它将 i=1 的原始值附加到注入document.write的输出中。
有趣的是,有人可能会在这里期待甚至另一个组合"13")))