【问题标题】:Adding a <script> node on top of <body> from javascript in <head>在 <head> 中的 javascript 在 <body> 之上添加一个 <script> 节点
【发布时间】:2016-06-25 19:57:29
【问题描述】:

我想插入一些javascript代码,

  1. 这应该在&lt;body&gt;run 中的其他javascript代码之前运行。

  2. 当他们在 &lt;body&gt; 中操作 html 时,它们在 &lt;body&gt; 中。

通常我会将此javascript放在&lt;script&gt;标签中,紧跟在&lt;body&gt;标签之后。但我不是直接写html。它是由一个程序为我生成的。 (react-storybook)。它的 API 允许在 &lt;head&gt; 中注入 html,但不能在 &lt;body&gt; 中注入。

<head>
  <script></script>  <-- I can inject a script tag here
</head>
<body>
  <script></script>  <-- I can't directly add this script tag but I need one here

  <script>other js</script>
</body>

我尝试将我的 js 放在 document load 事件处理程序中,但它们在 body 完全加载后运行,因此其他 js 已经运行。

我尝试将我的 js 直接放在头部然后我的 js 不能在 body 上使用 appendChild,因为此时 document.body 为空。

有没有办法插入满足上述两个要求的脚本标签,只访问&lt;head&gt;

【问题讨论】:

  • 你的意思是把脚本的加载放在那里,还是带有函数的脚本标签?
  • “我尝试将我的 js 直接放在头部,然后我的 js 无法在 body 上使用 appendChild,因为此时 document.body 为空。” 等等,您是想将元素输出到正文,还是作用于 其他 代码正在写入正文的元素?
  • 您能解释一下,这样做有什么必要吗?
  • T.J 只是将元素输出到正文。

标签: javascript html


【解决方案1】:

如果不破坏您尝试使用的工具并做一些相当讨厌的事情,我看不出有任何方法可以做到这一点。我怀疑有更好的方法来解决您的根本问题。

我尝试将我的 js 直接放在头部然后我的 js 无法在 body 上使用 appendChild,因为此时 document.body 为空...

很糟糕而且可能是个坏主意,但你可以强制启动正文:

document.write("<body>");

此时,解析器将创建body 元素。然后你可以使用appendChild(或者继续使用邪恶的document.write)。当身体在正常过程中再次启动时,第二个开始标签将被忽略。

不是个好主意。 react-storybook 是开源的。如果还没有办法用它来实现您的实际目标,我建议您对其进行修改,而不是像上面那样做。

这是一个示例,已在 Chrome、Firefox、IE11、IE8 和 iOS Safari 中测试并运行。
(Live copy on JSBin):

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>Example</title>
  <script>
    (function() {
      var d = document.createElement("div");
      d.innerHTML =
        "This is the element appended to the body element " +
        "that we forced to be created using the evil " +
        "<code>document.write</code>";
      document.write("<body>");
      document.body.appendChild(d);
    })();
  </script>
</head>
<body>
<div>
This is the first element inside the body element in the markup.
</div>
</body>
</html>

【讨论】:

  • 嗯。这太出乎意料了。
  • @Quentin:是的,document.write 和解析器之间的关系是交互式的,有点违反直觉,最好不要使用 document.write。 :-)
  • 谢谢,我现在可以使用它。最终将分叉故事书并提交 PR 以获得更好的东西。
【解决方案2】:

我尝试将我的 js 直接放在头部然后我的 js 不能在 body 上使用 appendChild,因为此时 document.body 为空。

MutationObserver 来救援!
你可以简单地等待&lt;body&gt;标签被解析:

<!DOCTYPE html>
<html>
<head>
    <title>Test</title>
    <script>
new MutationObserver(function(records, self)
{
    for(var i = 0; i < records.length; ++i)
    {
        for(var j = 0; j < records[i].addedNodes.length; ++j)
        {
            if(records[i].addedNodes[j].nodeName == 'BODY')
            {
                self.disconnect();
                console.log('herp');
                /*
                At this point, the body exists, but nothing inside it has been parsed yet.
                document.body might be available, but to be safe, you can use:
                var body = records[i].addedNodes[j];
                */
            }
        }
    }
}).observe(document.documentElement,
{
    childList: true,
});
    </script>
</head>
<body>
    <script>console.log('derp');</script>
</body>
</html>

将它保存到一个 HTML 文件,在浏览器中打开它,你应该会在控制台中看到它(表明 "herp" 部分在 "derp" 部分之前运行(注意:如果页面加载后打开控制台,但“herp”部分实际上仍在“derp”之前运行)):

herp
derp

(注意:上面的代码不能作为栈sn-p工作,因为所有的东西都放在&lt;body&gt;标签里面。)

现在为了安全起见,我会添加一个检查以查看是否已设置 document.body,如果不是这样,则仅设置 MutationObserver:

<!DOCTYPE html>
<html>
<head>
    <title>Test</title>
    <script>
function onBodyLoaded(body)
{
    console.log('herp');
    /* Do whatever you want with "body" here. */
}
if(document.body)
{
    onBodyLoaded(document.body)
}
else
{
    new MutationObserver(function(records, self)
    {
        for(var i = 0; i < records.length; ++i)
        {
            for(var j = 0; j < records[i].addedNodes.length; ++j)
            {
                if(records[i].addedNodes[j].nodeName == 'BODY')
                {
                    self.disconnect();
                    onBodyLoaded(records[i].addedNodes[j]);
                }
            }
        }
    }).observe(document.documentElement,
    {
        childList: true,
    });
}
    </script>
</head>
<body>
    <script>console.log('derp');</script>
</body>
</html>

这样您可能根本不需要在正文中添加&lt;script&gt; 标签,只需将要在其中运行的代码放在onBodyLoaded 函数中即可。
如果你确实需要添加脚本标签,你可以这样做:

function onBodyLoaded(body)
{
    body.appendChild(document.createElement('script')).src = 'https://example.com/my.js';
}

function onBodyLoaded(body)
{
    body.appendChild(document.createElement('script')).innerHTML = 'document.write("hi there")';
}

请注意,IE 10 及更早版本不支持MutationObserver。不过,IE 11 和这十年的任何其他浏览器都应该可以工作。

【讨论】:

  • 旁注:IE9 和 IE10 都支持旧的 Mutation Events(无论如何都足够做上面的事情了),所以即使在 IE9 上付出足够的努力,也可以追求这种技术。
  • @T.J.Crowder 感谢您提供信息。由于缺少 IE 9 无法测试,但 this polyfill 看起来应该可以为 IE 9 和 IE 10 完成工作。如果我有时间,我可能会稍后尝试修复 IE 11 的行为。
猜你喜欢
  • 2012-06-22
  • 2011-12-06
  • 2013-06-04
  • 1970-01-01
  • 2014-06-16
  • 1970-01-01
  • 2011-08-16
  • 1970-01-01
  • 2021-08-23
相关资源
最近更新 更多