HTML 注入后使用 CDN 或外部域执行内联脚本后执行的脚本文件
Script File Executing After Inline Script With CDN or External Domain On HTML injection
我在将 HTML 注入已加载的 DOM 时遇到问题,其中在下载脚本文件后加载内联 javascript。据我所知,这不应该是异步的,内联脚本应该在脚本文件之后执行。如果域名与调用页面相同,则此方法有效,但使用 CDN 甚至子域也可以做同样的事情。有什么我应该做的来修改我如何称呼这些吗?我发誓这在我打开 CDN 一个多星期之前就奏效了,但也许我从来没有发现这个问题。
控制台
Loading Inline Script
VM1400:3 Uncaught TypeError: Cannot read property 'init' of undefined(anonymous function)
app.members.event.js?v=204&_=1453644424985:5 Loading Script File
app.members.event.js?v=204&_=1453644424985:71 Finished Script File
Javascript
<script type="text/javascript" src="https://test.azureedge.net/Areas/Directors/scripts/app.members.event.js?v=204"></script>
<script type="text/javascript">
console.log('Loading Inline Script');
app.viewModel.members.event.init();
console.log('Finished Inline Script');
一种方法是使用 jquery's getScript() 函数。
但最好使用原生 javascript 加载脚本文件,然后 运行 内联脚本。
可能是我没理解清楚问题
编辑:
这是 HTML5 规范中有关脚本元素的引述。
If the element has a src content attribute, run these substeps:
Let src be the value of the element's src attribute.
If src is the empty string, queue a task to fire a simple event named
error at the element, and abort these steps.
Resolve src relative to the element.
If the previous step failed, queue a task to fire a simple event named
error at the element, and abort these steps.
Do a potentially CORS-enabled fetch of the resulting absolute URL,
with the mode being the current state of the element's crossorigin
content attribute, the origin being the origin of the script element's
Document, and the default origin behaviour set to taint.
The resource obtained in this fashion can be either CORS-same-origin
or CORS-cross-origin. This only affects how error reporting happens.
For performance reasons, user agents may start fetching the script (as
defined above) as soon as the src attribute is set, instead, in the
hope that the element will be inserted into the document (and that the
crossorigin attribute won't change value in the meantime). Either way,
once the element is inserted into the document, the load must have
started as described in this step. If the UA performs such
prefetching, but the element is never inserted in the document, or the
src attribute is dynamically changed, or the crossorigin attribute is
dynamically changed, then the user agent will not execute the script
so obtained, and the fetching process will have been effectively
wasted.
Then, the first of the following options that describes the situation
must be followed:
If the element has a src attribute, and the element has a defer
attribute, and the element has been flagged as "parser-inserted", and
the element does not have an async attribute The element must be added
to the end of the list of scripts that will execute when the document
has finished parsing associated with the Document of the parser that
created the element.
The task that the networking task source places on the task queue once
the fetching algorithm has completed must set the element's "ready to
be parser-executed" flag. The parser will handle executing the script.
If the element has a src attribute, and the element has been flagged
as "parser-inserted", and the element does not have an async attribute
The element is the pending parsing-blocking script of the Document of
the parser that created the element. (There can only be one such
script per Document at a time.)
The task that the networking task source places on the task queue once
the fetching algorithm has completed must set the element's "ready to
be parser-executed" flag. The parser will handle executing the script.
If the element does not have a src attribute, and the element has been
flagged as "parser-inserted", and either the parser that created the
script is an XML parser or it's an HTML parser whose script nesting
level is not greater than one, and the Document of the HTML parser or
XML parser that created the script element has a style sheet that is
blocking scripts The element is the pending parsing-blocking script of
the Document of the parser that created the element. (There can only
be one such script per Document at a time.)
Set the element's "ready to be parser-executed" flag. The parser will
handle executing the script.
If the element has a src attribute, does not have an async attribute,
and does not have the "force-async" flag set The element must be added
to the end of the list of scripts that will execute in order as soon
as possible associated with the Document of the script element at the
time the prepare a script algorithm started.
The task that the networking task source places on the task queue once
the fetching algorithm has completed must run the following steps:
If the element is not now the first element in the list of scripts
that will execute in order as soon as possible to which it was added
above, then mark the element as ready but abort these steps without
executing the script yet.
Execution: Execute the script block corresponding to the first script
element in this list of scripts that will execute in order as soon as
possible.
Remove the first element from this list of scripts that will execute
in order as soon as possible.
If this list of scripts that will execute in order as soon as possible
is still not empty and the first entry has already been marked as
ready, then jump back to the step labeled execution.
If the element has a src attribute The element must be added to the
set of scripts that will execute as soon as possible of the Document
of the script element at the time the prepare a script algorithm
started.
The task that the networking task source places on the task queue once
the fetching algorithm has completed must execute the script block and
then remove the element from the set of scripts that will execute as
soon as possible.
Otherwise The user agent must immediately execute the script block,
even if other scripts are already executing. Fetching an external
script must delay the load event of the element's document until the
task that is queued by the networking task source once the resource
has been fetched (defined above) has been run.
由此我认为您的 "external" 文件是在内联脚本块之后加载的。因此,我会使用 jquery 中的 "getScript()" 函数来确保脚本在内联脚本块之前加载。
我有两种说法:
可能是外部脚本中的某些东西延迟了 app.viewModel.members 对象的创建(超时或需要一段时间才能完成的事件处理程序)。这可以通过在内联脚本中设置较长的超时时间 (f.i.5000+ms) 轻松测试,然后检查模型对象是否存在。
加载同源脚本时发生了一些奇怪的事情。
在这种情况下,您可以尝试通过执行以下操作来延迟内联脚本的执行:
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function(event) {
app.viewModel.members.event.init();
});
</script>
或者只是将内联代码放在外部 .js 文件中并使用 'deferred' 标志调用它:
<script type="text/javascript" src="https://test.azureedge.net/Areas/Directors/scripts/app.members.event.js?v=204"></script>
<script type="text/javascript" src="{link-to-external-js-file}" defer></script>
这是注入场景中的常见问题。它的发生是由于脚本可用性的可变延迟,以及由于各种浏览器上的并行和不同实现。
有 3 个选项,取决于源代码是否可供编辑,以及脚本文件之间是否存在超过 2 个依赖关系。
方案1.在脚本标签中使用defer属性
如果两个脚本都是远程的(即非内联),则可以使用此选项
"defer" 向浏览器指示脚本必须在文档被解析后执行(引用自 MDN)。这仅适用于具有 "src" 属性的远程(非内联)脚本。
https://html.spec.whatwg.org/multipage/scripting.html#attr-script-defer
您可以像下面这样使用它。
主要浏览器都支持 Defer,我在 Chrome、Firefox、Tizen 上的 Webkit 和 Safari 上进行了验证:
https://developer.mozilla.org/en/docs/Web/HTML/Element/script#Browser_compatibility
要提供上述案例的具体示例,请参阅下文。请注意,以下已在 Firefox、Chrome、IE11、iPhone 上的 Safari 和 Tizen 上的 Webkit 上得到验证。
案例一:
许多 Javascript 个文件 - 全部独立:
如果没有依赖,"defer"属性可以让HTML快速加载。下载后脚本接管,没有问题(假设 onload 等已处理)。
案例二:
两个 javascript 文件 test1.js 和 test2.js - 一个依赖于另一个:
如果test2.js依赖于test1.js的加载,那么test2.js"only"的脚本标签应该有defer属性。
此用法显示在
http://www.gpupowered.org/loadtest/2_defer.html
中显示了不正确的用法
http://www.gpupowered.org/loadtest/no_defer.html(两个脚本都没有 defer 标签 - 这失败了)
http://www.gpupowered.org/loadtest/all_defer.html(两个脚本都有 defer 标签 - 这也失败了)
不工作的异步用法是,
http://gpupowered.org/loadtest/2_async.html(失败)
"defer"哪里不符合需求?
如果功能被分成几个 JS 文件(比如 n),并且在第 "n" 个文件开始处理一些变量之前需要下载所有 "n-1",即使 "defer" 属性可能出现在所有脚本源标记上,它变得无关紧要,因为接收它们的顺序是不确定的。
关于 defer 的更多背景知识和延迟加载的各种选项(不包括 multi defer 情况)
http://www.html5rocks.com/en/tutorials/speed/script-loading/
https://developer.mozilla.org/en/docs/Web/HTML/Element/script
选项 2:使用状态变量
如果可以将一些额外的状态变量添加到两个 javascript 源文件中,则可以使用此选项。
该方法依赖于依赖js文件中的命名变量,以及使用依赖文件的js文件中的命名函数。如果在用户文件试图访问它的功能时依赖文件没有加载,它会退出并在真正加载时被回调。
下面的 html 文件对此进行了演示。
http://gpupowered.org/loadtest/variable.html(工作正常)
如果加载需要重复进行(即加载多个同名文件等),则此选项不起作用。
选项 3:本机脚本加载器
在这种情况下,有多个 javascript 个文件相互依赖。
对于使用 defer 或 async 或其他规范提供的标签的情况,没有解决方案。对于我在 gpupowered.org 的远程实验室中的用例,我必须使用 XMLHttpRequest 实现我自己的本机脚本加载器,下面的 link 中提供了它的源代码。这使用工作线程,因为我拥有的一些纹理相当大。回调函数可用于根据应用程序需要实现依赖逻辑。例如,统计所有加载的脚本,然后触发完全执行等。
https://github.com/prabindh/gpupowered.gl/blob/master/worker/worker_object_loader.js
jquery 脚本加载器也使用 HTTP 请求,但我没有检查它是否使用 worker 进行加载。
https://api.jquery.com/jquery.getscript/
使用这个:
<script type="text/javascript" src="https://test.azureedge.net/Areas/Directors/scripts/app.members.event.js?v=204&onload=onloadCallback"></script
<script type="text/javascript">function onloadCallback(){
app.viewModel.members.event.init();}</script>
function onloadCallback(){
app.viewModel.members.event.init();
}
我在将 HTML 注入已加载的 DOM 时遇到问题,其中在下载脚本文件后加载内联 javascript。据我所知,这不应该是异步的,内联脚本应该在脚本文件之后执行。如果域名与调用页面相同,则此方法有效,但使用 CDN 甚至子域也可以做同样的事情。有什么我应该做的来修改我如何称呼这些吗?我发誓这在我打开 CDN 一个多星期之前就奏效了,但也许我从来没有发现这个问题。
控制台
Loading Inline Script
VM1400:3 Uncaught TypeError: Cannot read property 'init' of undefined(anonymous function)
app.members.event.js?v=204&_=1453644424985:5 Loading Script File
app.members.event.js?v=204&_=1453644424985:71 Finished Script File
Javascript
<script type="text/javascript" src="https://test.azureedge.net/Areas/Directors/scripts/app.members.event.js?v=204"></script>
<script type="text/javascript">
console.log('Loading Inline Script');
app.viewModel.members.event.init();
console.log('Finished Inline Script');
一种方法是使用 jquery's getScript() 函数。
但最好使用原生 javascript 加载脚本文件,然后 运行 内联脚本。
可能是我没理解清楚问题
编辑: 这是 HTML5 规范中有关脚本元素的引述。
If the element has a src content attribute, run these substeps:
Let src be the value of the element's src attribute.
If src is the empty string, queue a task to fire a simple event named error at the element, and abort these steps.
Resolve src relative to the element.
If the previous step failed, queue a task to fire a simple event named error at the element, and abort these steps.
Do a potentially CORS-enabled fetch of the resulting absolute URL, with the mode being the current state of the element's crossorigin content attribute, the origin being the origin of the script element's Document, and the default origin behaviour set to taint.
The resource obtained in this fashion can be either CORS-same-origin or CORS-cross-origin. This only affects how error reporting happens.
For performance reasons, user agents may start fetching the script (as defined above) as soon as the src attribute is set, instead, in the hope that the element will be inserted into the document (and that the crossorigin attribute won't change value in the meantime). Either way, once the element is inserted into the document, the load must have started as described in this step. If the UA performs such prefetching, but the element is never inserted in the document, or the src attribute is dynamically changed, or the crossorigin attribute is dynamically changed, then the user agent will not execute the script so obtained, and the fetching process will have been effectively wasted.
Then, the first of the following options that describes the situation must be followed:
If the element has a src attribute, and the element has a defer attribute, and the element has been flagged as "parser-inserted", and the element does not have an async attribute The element must be added to the end of the list of scripts that will execute when the document has finished parsing associated with the Document of the parser that created the element.
The task that the networking task source places on the task queue once the fetching algorithm has completed must set the element's "ready to be parser-executed" flag. The parser will handle executing the script.
If the element has a src attribute, and the element has been flagged as "parser-inserted", and the element does not have an async attribute The element is the pending parsing-blocking script of the Document of the parser that created the element. (There can only be one such script per Document at a time.)
The task that the networking task source places on the task queue once the fetching algorithm has completed must set the element's "ready to be parser-executed" flag. The parser will handle executing the script.
If the element does not have a src attribute, and the element has been flagged as "parser-inserted", and either the parser that created the script is an XML parser or it's an HTML parser whose script nesting level is not greater than one, and the Document of the HTML parser or XML parser that created the script element has a style sheet that is blocking scripts The element is the pending parsing-blocking script of the Document of the parser that created the element. (There can only be one such script per Document at a time.)
Set the element's "ready to be parser-executed" flag. The parser will handle executing the script.
If the element has a src attribute, does not have an async attribute, and does not have the "force-async" flag set The element must be added to the end of the list of scripts that will execute in order as soon as possible associated with the Document of the script element at the time the prepare a script algorithm started.
The task that the networking task source places on the task queue once the fetching algorithm has completed must run the following steps:
If the element is not now the first element in the list of scripts that will execute in order as soon as possible to which it was added above, then mark the element as ready but abort these steps without executing the script yet.
Execution: Execute the script block corresponding to the first script element in this list of scripts that will execute in order as soon as possible.
Remove the first element from this list of scripts that will execute in order as soon as possible.
If this list of scripts that will execute in order as soon as possible is still not empty and the first entry has already been marked as ready, then jump back to the step labeled execution.
If the element has a src attribute The element must be added to the set of scripts that will execute as soon as possible of the Document of the script element at the time the prepare a script algorithm started.
The task that the networking task source places on the task queue once the fetching algorithm has completed must execute the script block and then remove the element from the set of scripts that will execute as soon as possible.
Otherwise The user agent must immediately execute the script block, even if other scripts are already executing. Fetching an external script must delay the load event of the element's document until the task that is queued by the networking task source once the resource has been fetched (defined above) has been run.
由此我认为您的 "external" 文件是在内联脚本块之后加载的。因此,我会使用 jquery 中的 "getScript()" 函数来确保脚本在内联脚本块之前加载。
我有两种说法:
可能是外部脚本中的某些东西延迟了 app.viewModel.members 对象的创建(超时或需要一段时间才能完成的事件处理程序)。这可以通过在内联脚本中设置较长的超时时间 (f.i.5000+ms) 轻松测试,然后检查模型对象是否存在。
加载同源脚本时发生了一些奇怪的事情。
在这种情况下,您可以尝试通过执行以下操作来延迟内联脚本的执行:
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function(event) {
app.viewModel.members.event.init();
});
</script>
或者只是将内联代码放在外部 .js 文件中并使用 'deferred' 标志调用它:
<script type="text/javascript" src="https://test.azureedge.net/Areas/Directors/scripts/app.members.event.js?v=204"></script>
<script type="text/javascript" src="{link-to-external-js-file}" defer></script>
这是注入场景中的常见问题。它的发生是由于脚本可用性的可变延迟,以及由于各种浏览器上的并行和不同实现。
有 3 个选项,取决于源代码是否可供编辑,以及脚本文件之间是否存在超过 2 个依赖关系。
方案1.在脚本标签中使用defer属性
如果两个脚本都是远程的(即非内联),则可以使用此选项
"defer" 向浏览器指示脚本必须在文档被解析后执行(引用自 MDN)。这仅适用于具有 "src" 属性的远程(非内联)脚本。
https://html.spec.whatwg.org/multipage/scripting.html#attr-script-defer
您可以像下面这样使用它。
主要浏览器都支持 Defer,我在 Chrome、Firefox、Tizen 上的 Webkit 和 Safari 上进行了验证:
https://developer.mozilla.org/en/docs/Web/HTML/Element/script#Browser_compatibility
要提供上述案例的具体示例,请参阅下文。请注意,以下已在 Firefox、Chrome、IE11、iPhone 上的 Safari 和 Tizen 上的 Webkit 上得到验证。
案例一:
许多 Javascript 个文件 - 全部独立:
如果没有依赖,"defer"属性可以让HTML快速加载。下载后脚本接管,没有问题(假设 onload 等已处理)。
案例二:
两个 javascript 文件 test1.js 和 test2.js - 一个依赖于另一个:
如果test2.js依赖于test1.js的加载,那么test2.js"only"的脚本标签应该有defer属性。
此用法显示在
http://www.gpupowered.org/loadtest/2_defer.html
中显示了不正确的用法http://www.gpupowered.org/loadtest/no_defer.html(两个脚本都没有 defer 标签 - 这失败了) http://www.gpupowered.org/loadtest/all_defer.html(两个脚本都有 defer 标签 - 这也失败了)
不工作的异步用法是,
http://gpupowered.org/loadtest/2_async.html(失败)
"defer"哪里不符合需求?
如果功能被分成几个 JS 文件(比如 n),并且在第 "n" 个文件开始处理一些变量之前需要下载所有 "n-1",即使 "defer" 属性可能出现在所有脚本源标记上,它变得无关紧要,因为接收它们的顺序是不确定的。
关于 defer 的更多背景知识和延迟加载的各种选项(不包括 multi defer 情况) http://www.html5rocks.com/en/tutorials/speed/script-loading/
https://developer.mozilla.org/en/docs/Web/HTML/Element/script
选项 2:使用状态变量
如果可以将一些额外的状态变量添加到两个 javascript 源文件中,则可以使用此选项。
该方法依赖于依赖js文件中的命名变量,以及使用依赖文件的js文件中的命名函数。如果在用户文件试图访问它的功能时依赖文件没有加载,它会退出并在真正加载时被回调。
下面的 html 文件对此进行了演示。
http://gpupowered.org/loadtest/variable.html(工作正常)
如果加载需要重复进行(即加载多个同名文件等),则此选项不起作用。
选项 3:本机脚本加载器
在这种情况下,有多个 javascript 个文件相互依赖。
对于使用 defer 或 async 或其他规范提供的标签的情况,没有解决方案。对于我在 gpupowered.org 的远程实验室中的用例,我必须使用 XMLHttpRequest 实现我自己的本机脚本加载器,下面的 link 中提供了它的源代码。这使用工作线程,因为我拥有的一些纹理相当大。回调函数可用于根据应用程序需要实现依赖逻辑。例如,统计所有加载的脚本,然后触发完全执行等。
https://github.com/prabindh/gpupowered.gl/blob/master/worker/worker_object_loader.js
jquery 脚本加载器也使用 HTTP 请求,但我没有检查它是否使用 worker 进行加载。 https://api.jquery.com/jquery.getscript/
使用这个:
<script type="text/javascript" src="https://test.azureedge.net/Areas/Directors/scripts/app.members.event.js?v=204&onload=onloadCallback"></script
<script type="text/javascript">function onloadCallback(){
app.viewModel.members.event.init();}</script>
function onloadCallback(){
app.viewModel.members.event.init();
}