Challenge-Me:JavaScript 包含引擎 - 第二部分





5.00/5 (21投票s)
如何在其他 JavaScript 文件中包含 JavaScript 文件。
前言
这是由三部分组成的文章的第二部分,为了更好地理解,我建议在阅读此部分之前阅读第一部分。
第二部分
来自前一部分
挑战
创建一个引擎,实现 JavaScript 中缺少的功能,以便在其他 JavaScript 文件中包含 JavaScript 文件。
解决方案
在第一部分中,我提出了一个解决方案,任何对动态 HTML 以及外部 JavaScript 文件的加载和执行方式有所了解的人都可以轻松推断和实现该解决方案。但它有一个严重的缺点:所有文件应该只包含函数声明和对象声明(但前提是它们与来自其他文件的对象无关)。
因此,在本部分中,我们将看到一种完全不同的方法。
解决方案 2
逻辑
让我们重新思考一下解决方案。
理想情况下,只应该执行文件的 $include
函数部分 – 我们将这部分命名为**header**,然后,在适当的时候,执行其余部分 – body。 但正如我上面所说,这是不可能的。 因此,如果我们在谈论 header 和 body,那么我们有一个逻辑拆分文件,并且在存在逻辑拆分的地方,是否不能进行物理拆分? 一个头文件(带有 *jsh* 扩展名)和一个源文件(普通的 *js* 文件)? 类似于 C 语言?
让我们检查一下这种方法的影响。
我们首先加载所有 header,然后从中我们可以确定必须加载源文件的顺序。 完美。 正是我们需要的。 从之前的实现中,我们将保留让我们指定相对于 *IncludingEngine.js* 文件的路径而不是当前 HTML 文档的路径的代码。
我进行了实现,但出现了另一个问题:嵌入在 HTML 文档中的 JavaScript 代码在完成所有动态添加到文档 head 中的外部文件的执行之前就被执行了。
我做了一些研究 - 阅读和测试。 虽然有些人认为外部 JavaScript 文件是在单独的线程中加载的,因此,你永远无法确定加载顺序和执行顺序,但我不能同意后一部分。
从我的测试来看,浏览器(尤其是 Firefox,不能 100% 确定 Internet Explorer)确实似乎在不同的线程中加载脚本; **但是**,如果一个脚本在之前定义的脚本之前完成加载,则其执行会延迟到之前的脚本完成加载和执行之后,然后才会被执行; 这样,所有脚本都以正确的顺序执行。
不幸的是,如果你动态插入或删除脚本对象,此执行顺序会被搞乱。 现在我们处于不知道执行顺序的情况下,但我们可以通过仅在前一个脚本加载并执行后才加载一个脚本来避免这种情况。
但是,嵌入的 JavaScript 代码会在外部脚本执行之前很久(大多数情况下)被执行,但反过来也是有可能的。
因此,只有在外部脚本全部执行后,我们 HTML 文档中的代码才能使用来自外部脚本的代码。
因此,我们必须有一个像 window.onload
这样的事件来告诉我们外部文件何时完成执行。 由于外部脚本有可能首先执行,因此处理这两种情况的事件会更好。 更直观的是,当“一切就绪”时将执行的函数。 出于显而易见的原因,我们将此函数称为 Main
。
实现
var IncludingEngine = {};
IncludingEngine.FindPath = function()
{
var scripts = document.getElementsByTagName("script");
var foundNo = 0;
for (var i=0; i<scripts.length; i++)
{
if (scripts[i].src.match(this.FileName))
{
this.Path = scripts[i].src.replace(this.FileName, "");
foundNo ++;
}
}
if (foundNo == 0)
throw new Error("The name of this file isn't " +
this.FileName + "!\r\nPlease change it back!");
if (foundNo > 1)
throw new Error("There are " + foundNo + " files with the name " +
this.FileName + "!\r\nThere can be only one!");
}
IncludingEngine.Init = function ()
{
this.FileName = "IncludingEngine.js";
this.Path = "";//the root path of the engine;
//all files included by this engine will be relative to this path
this.FilesLoaded = new Array(); //will store the files already loaded
//in order not to load (execute) a file many times
//status files
//0 - just identified
//1 - loading
//2 - loaded
this.HeaderFilesStatus = new Array();//hashtable with the status of the header files
this.SourceFileStatus = new Array();//hashtable with the status of the source files
this.SourceFiles = new Array();//array with the source files
//(used to create the head tag with them in the reverse order)
this.FindPath();
}
//Loads the header files (.jsh)
IncludingEngine.HeaderScriptLoad = function()
{
if (this.readyState)
if (this.readyState != "complete")
return;
IncludingEngine.HeaderFilesStatus[this.src] = 2;
var done = true;
for(var el in IncludingEngine.HeaderFilesStatus)
if (IncludingEngine.HeaderFilesStatus[el] != 2)
done = false;
if (done)
{
var head = document.getElementsByTagName("head")[0];
for (var k = IncludingEngine.SourceFiles.length - 1; k >= 0; k--)
{
if (IncludingEngine.SourceFileStatus[IncludingEngine.SourceFiles[k]] == 0)
{
var script = document.createElement("script");
script.src = IncludingEngine.SourceFiles[k];
script.type = "text/javascript";
script.onload = script.onreadystatechange =
IncludingEngine.SourceScriptLoad;
IncludingEngine.SourceFileStatus[IncludingEngine.SourceFiles[k]] = 1
head.appendChild(script);
}
}
}
var head = document.getElementsByTagName("head")[0];
head.removeChild(this);
}
//Loads the source files (.js)
IncludingEngine.SourceScriptLoad = function()
{
if (this.readyState)
if (this.readyState != "complete")
return;
IncludingEngine.SourceFileStatus[this.src] = 2;
var done = true;
for(var el in IncludingEngine.SourceFileStatus)
if (IncludingEngine.SourceFileStatus[el] != 2)
done = false;
if (done)
{
if (Main)
{
if (typeof Main == "function")
Main();
}
}
}
function $include()
{
var head = document.getElementsByTagName('head')[0];
for (var i = 0; i < arguments.length; i++)
{
if (IncludingEngine.HeaderFilesStatus[arguments[i] + ".jsh"] == null)
{
var script = document.createElement("script");
script.src = IncludingEngine.Path + arguments[i] + ".jsh";
script.type = 'text/javascript';
script.onload = script.onreadystatechange = IncludingEngine.HeaderScriptLoad;
IncludingEngine.SourceFiles.push(IncludingEngine.Path + arguments[i] + ".js");
IncludingEngine.SourceFileStatus[IncludingEngine.Path + arguments[i] + ".js"] = 0;
IncludingEngine.HeaderFilesStatus[script.src] = 1;
head.appendChild(script);
}
}
}
IncludingEngine.Init();
我认为该实现非常不言自明。 Init
大部分与第一个解决方案相同,FindPath
也相同。 HeaderScriptLoad
用于加载 header 并生成正确的加载源脚本的顺序,SourceScriptLoad
用于按照先前生成的顺序加载源文件。
正如你所观察到的,我没有检查循环依赖项; 这将在下一部分中实现。
注释
- 我会编辑这篇文章,以便在我发布后立即添加指向最后一部分的链接。
- 可以在 IncludingEngine.jsFramework.com 找到此实现的最新版本。
以下是“JavaScript 包含引擎”系列文章的所有部分:第一部分、第二部分、第三部分。 此外,你可能想访问 IncludingEngine 网站以获取更多信息。