ensure - 按需加载 JavaScripts/HTML/CSS






4.97/5 (21投票s)
一个轻量级的 JavaScript 库,提供一个方便的 "ensure" 函数,允许您按需加载 JavaScript、HTML、CSS,然后执行您的代码。ensure 确保相关的 JavaScript 和 HTML 片段在执行使用它们的代码之前已经存在于浏览器 DOM 中。
引言
ensure 是一个轻量级的 JavaScript 库,提供一个方便的 ensure
函数,允许您按需加载 JavaScript、HTML、CSS,然后执行您的代码。ensure 确保相关的 JavaScript 和 HTML 片段在执行使用它们的代码之前已经存在于浏览器 DOM 中。
例如
ensure( { js: "Some.js" }, function()
{
SomeJS(); // The function SomeJS is available in Some.js only
});
从 这里 下载最新的代码。
您可以在 此网站 上看到 ensure 的实际应用。
ensure 支持 jQuery、Microsoft ASP.NET AJAX 和 Prototype 框架。这意味着您可以在任何使用以上任一框架的 HTML、ASP.NET、PHP、JSP 页面上使用它。
背景
具有丰富客户端效果(动画、验证、菜单、弹出窗口)和 AJAX 网站的网站需要将大量的 JavaScript、HTML 和 CSS 在同一个网页上交付给浏览器。因此,富网页的初始加载时间会显著增加,因为下载必需的组件需要相当长的时间。此外,一次性交付所有可能的组件会使页面变得臃肿,浏览器在响应操作时会变得迟钝。您有时会看到下拉菜单卡顿、弹出窗口出现缓慢、窗口滚动迟钝等情况。
解决方案不是在初始加载时交付所有可能的 HTML、JavaScript 和 CSS,而是根据需要交付它们。例如,当用户将鼠标悬停在菜单栏上时,下载下拉菜单效果所需的 JavaScript 和 CSS,以及出现在下拉菜单中的菜单 HTML。同样,如果您有客户端验证,则在用户单击“提交”按钮时交付客户端验证库、相关的警告 HTML 片段和 CSS。如果您有一个按需显示页面的 AJAX 站点,则只在用户执行导致 AJAX 调用的操作时才加载 AJAX 库本身。因此,通过将一个包含大量 HTML、CSS 和 JavaScript 的复杂页面分解成更小的部分,您可以显著降低初始交付的大小,从而非常快速地加载初始页面,并为用户提供快速流畅的浏览体验。
ensure 的优点
ensure 避免了在初始阶段交付不必要的 JavaScript、HTML 和 CSS,而是在需要时按需加载它们。由 ensure 加载的 JavaScript、HTML 和 CSS 会保留在浏览器中,下次 ensure 使用相同的 JavaScript、CSS 或 HTML 调用时,它不会重新加载它们,从而节省了重复下载。
例如,您可以使用 ensure 按需下载 JavaScript
ensure( { js: "Some.js" }, function()
{
SomeJS(); // The function SomeJS is available in Some.js only
});
上面的代码确保在执行代码之前 Some.js 可用。如果 SomeJS.js 已经加载,它会立即执行函数。否则,它会下载 Some.js,等待其完全加载,然后才执行函数。因此,当您只需要在某些用户操作时才需要 Some.js 时,可以避免在初始时交付它。
同样,您可以等待某些 HTML 片段可用,例如一个弹出对话框。您无需在默认网页上交付您将要显示给用户的所有可能弹出框的 HTML。您可以在需要时获取 HTML。
ensure( {html: "Popup.html"}, function()
{
// The element "Popup" is available only in Popup.html
document.getElementById("Popup").style.display = "";
});
上面的代码从 "Popup.html" 下载 HTML,并将其添加到文档的 body 中,然后触发函数。因此,您的代码可以安全地使用该 HTML 中的 UI 元素。
您可以在一个 ensure
调用中混合搭配 JavaScript、HTML 和 CSS。例如
ensure( { js: "popup.js", html: "popup.html", css: "popup.css" }, function()
{
PopupManager.show();
});
您还可以指定多个 JavaScript、HTML 或 CSS 文件,以确保在执行代码之前所有这些文件都可用。
ensure( { js: ["blockUI.js","popup.js"], html: ["popup.html", "blockUI.html"],
css: ["blockUI.css", "popup.css"] }, function()
{
BlockUI.show();
PopupManager.show();
});
您可能会认为您将在 JavaScript 代码中编写大量 ensure
代码,这将导致 JavaScript 文件比以前更大。为了节省 JavaScript 的大小,您可以为常用的文件定义简写。
var JQUERY = { js: "jquery.js" };
var POPUP = { js: ["blockUI.js","popup.js"], html: ["popup.html", "blockUI.html"],
css: ["blockUI.css", "popup.css"] };
...
...
ensure( JQUERY, POPUP, function() {
$("DeleteConfirmPopupDIV").show();
});
...
...
ensure( POPUP, function()
{
$("SaveConfirmationDIV").show();
);
在加载 HTML 时,您可以指定一个容器元素,ensure 可以在其中注入加载的 HTML。例如,您可以说加载 HtmlSnippet.html,然后将内容注入名为 "exampleDiv
" 的 DIV
中。
ensure( { html: ["popup.html", "blockUI.html"], parent: "exampleDiv"}, function(){});
您还可以指定与 HTML 一起加载的 JavaScript 和 CSS。
ensure 具有测试功能,您可以检查特定的 JavaScript 类或某些 UI 元素是否已可用。如果可用,它不会下载指定的组件,并立即执行您的代码。如果不可用,它会下载它们,然后执行您的代码。当您尝试使用某些实用函数或 UI 元素并希望确保它们已存在时,此功能非常方便。
ensure( {test:"Sys", js:"MicrosoftAjax.js"}, function(){ Sys.Application.init(); });
上面的示例检查 Microsoft AJAX 库的 Sys
类是否已存在。如果 Microsoft AJAX 库已加载,它就会存在。如果不存在,它会加载库,然后调用代码。
同样,您可以确保某个 UI 元素已存在
ensure( {test:"PopupDIV", js:"Popup.js", html:"popup.html"},
function()
{
document.getElementById("PopupDIV").style.display = "block";
});
这确保了 ID 为 PopupDIV
的 HTML 元素已存在。如果不存在,它会下载相关的 JavaScript/HTML,然后执行您的代码。
工作原理
该库只有一个 JavaScript 文件 - ensure.js。但是,它需要以下任何 JavaScript 框架。
- jQuery
- Microsoft ASP.NET AJAX
- 原型
首先是 ensure
函数的定义。
window.ensure = function( data, callback, scope )
{
if( typeof jQuery == "undefined" && typeof Sys == "undefined"
&& typeof Prototype == "undefined" )
return alert("jQuery, Microsoft ASP.NET AJAX or
Prototype library not found. One must be present for ensure to work");
// There's a test criteria which when false,
//the associated components must be loaded. But if true,
// no need to load the components
if( typeof data.test != "undefined" )
{
var test = function() { return data.test };
if( typeof data.test == "string" )
{
test = function()
{
// If there's no such Javascript variable and there's
// no such DOM element with ID then
// the test fails. If any exists, then test succeeds
return !(eval( "typeof " + data.test ) == "undefined"
&& document.getElementById(data.test) == null);
}
}
else if( typeof data.test == "function" )
{
test = data.test;
}
// Now we have test prepared, time to execute the test
// and see if it returns null, undefined or false in any
// scenario. If it does, then load the specified javascript/html/css
if( test() === false || typeof test() == "undefined" || test() == null )
new ensureExecutor(data, callback, scope);
// Test succeeded! Just fire the callback
else
callback();
}
else
{
// No test specified. So, load necessary javascript/html/css
// and execute the callback
new ensureExecutor(data, callback, scope);
}
}
然而,真正的工作是由 ensureExecutor
完成的。基本上,ensure
创建一个 ensureExecute
实例,并将相关数据、回调和作用域传递给它。加载内容和调用回调的实际工作是在 ensureExecutor
中完成的。
首先,ensureExecutor
对参数进行一些准备,并确保存在有效的参数。然后,它调用 init
函数来初始化当前可用的框架(jQuery/Microsoft AJAX/Prototype),以进行一些常见的 AJAX 操作。然后,它调用 load
函数来加载必需的组件并触发回调。
window.ensureExecutor.prototype = {
init : function()
{
// Fetch Javascript using Framework specific library
if( typeof jQuery != "undefined" )
{
this.getJS = HttpLibrary.loadJavascript_jQuery;
this.httpGet = HttpLibrary.httpGet_jQuery;
}
else if( typeof Prototype != "undefined" )
{
this.getJS = HttpLibrary.loadJavascript_Prototype;
this.httpGet = HttpLibrary.httpGet_Prototype;
}
else if( typeof Sys != "undefined" )
{
this.getJS = HttpLibrary.loadJavascript_MSAJAX;
this.httpGet = HttpLibrary.httpGet_MSAJAX;
}
else
{
throw "jQuery, Prototype or MS AJAX framework not found";
}
},
在这里,init
函数检查当前加载的框架,并据此初始化两个函数 getJS
和 httpGet
,它们分别加载外部脚本和外部 HTML。
load : function()
{
this.loadJavascripts( this.delegate( function() {
this.loadCSS( this.delegate( function() {
this.loadHtml( this.delegate( function() {
this.callback()
} ) )
} ) )
} ) );
},
load
函数按顺序调用 loadJavascripts
、loadCSS
和 loadHtml
。这确保在 JavaScript 成功加载后才加载 HTML,而 CSS 加载要么已完成,要么已经开始。
loadJavascripts
是一个棘手的函数。它通过创建 <script>
标签或使用 XMLHTTP
下载外部脚本来加载外部脚本。Safari 需要 XMLHTTP
,因为没有办法知道 <script>
标签何时已成功下载。
loadJavascripts : function(complete)
{
var scriptsToLoad = this.data.js.length;
if( 0 === scriptsToLoad ) return complete();
this.forEach(this.data.js, function(href)
{
if( HttpLibrary.isUrlLoaded(href) ||
this.isTagLoaded('script', 'src', href) )
{
scriptsToLoad --;
}
else
{
this.getJS({
url: href,
success: this.delegate(function(content)
{
scriptsToLoad --;
HttpLibrary.registerUrl(href);
}),
error: this.delegate(function(msg)
{
scriptsToLoad --;
if(typeof this.data.error == "function")
this.data.error(href, msg);
})
});
}
});
// wait until all the external scripts are downloaded
this.until({
test: function() { return scriptsToLoad === 0; },
delay: 50,
callback: this.delegate(function()
{
complete();
})
});
},
思路是发出脚本下载请求,并等待所有脚本下载完成。完成后,它会触发完成回调,然后调用 loadCSS
或 loadHTML
函数。
loadCSS
函数相对简单。唯一的注意事项是,在 Internet Explorer 6 中,您必须仅在代码在 window
对象的上下文执行时才添加 <link>
标签。我在 CodeProject 上关于 UFrame 的另一篇文章详细解释了这个问题。
loadCSS : function(complete)
{
if( 0 === this.data.css.length ) return complete();
var head = HttpLibrary.getHead();
this.forEach(this.data.css, function(href)
{
if( HttpLibrary.isUrlLoaded(href) || this.isTagLoaded('link', 'href', href) )
{
// Do nothing
}
else
{
var self = this;
try
{
(function(href, head)
{
var link = document.createElement('link');
link.setAttribute("href", href);
link.setAttribute("rel", "Stylesheet");
link.setAttribute("type", "text/css");
head.appendChild(link);
HttpLibrary.registerUrl(href);
}).apply(window, [href, head]);
}
catch(e)
{
if(typeof self.data.error == "function")
self.data.error(href, e.message);
}
}
});
complete();
}
最后,loadHTML
函数下载 HTML 并将其注入 document.body
或您指定的任何父容器元素中。
loadHtml : function(complete)
{
var htmlToDownload = this.data.html.length;
if( 0 === htmlToDownload ) return complete();
this.forEach(this.data.html, function(href)
{
if( HttpLibrary.isUrlLoaded(href) )
{
htmlToDownload --;
}
else
{
this.httpGet({
url: href,
success: this.delegate(function(content)
{
htmlToDownload --;
HttpLibrary.registerUrl(href);
var parent = (this.data.parent ||
document.body.appendChild(
document.createElement("div")));
if( typeof parent == "string" )
parent = document.getElementById(parent);
parent.innerHTML = content;
}),
error: this.delegate(function(msg)
{
htmlToDownload --;
if(typeof this.data.error == "function")
this.data.error(href, msg);
})
});
}
});
// wait until all the external scripts are downloaded
this.until({
test: function() { return htmlToDownload === 0; },
delay: 50,
callback: this.delegate(function()
{
complete();
})
});
就是这样。
等等,还有这个 HttpLibrary
类,它完成了最复杂的工作——加载和执行 JavaScript,以及进行 AJAX 调用。
这是如何使用 <SCRIPT>
标签加载外部脚本并知道脚本何时加载,以跨浏览器兼容的方式。
createScriptTag : function(url, success, error)
{
var scriptTag = document.createElement("script");
scriptTag.setAttribute("type", "text/javascript");
scriptTag.setAttribute("src", url);
scriptTag.onload = scriptTag.onreadystatechange = function()
{
if ( (!this.readyState || this.readyState == "loaded"
|| this.readyState == "complete") ) {
success();
}
};
scriptTag.onerror = function()
{
error(data.url + " failed to load");
};
var head = HttpLibrary.getHead();
head.appendChild(scriptTag);
},
看起来很简单,但为了使其在所有流行浏览器中完美运行,付出了很多心血。但是,Safari 2 不支持 onload
或 onreadystatechange
事件。因此,对于 Safari,技巧是发出 XMLHTTP
请求来下载脚本,然后执行它。但是,这意味着您无法在 Safari 上确保来自外部域的脚本,因为 XMLHTTP
调用仅适用于当前域。
在这里,您可以看到下载脚本并执行它的三种方法。
loadJavascript_jQuery : function(data)
{
if( HttpLibrary.browser.safari )
{
return jQuery.ajax({
type: "GET",
url: data.url,
data: null,
success: function(content)
{
HttpLibrary.globalEval(content);
data.success();
},
error: function(xml, status, e)
{
if( xml && xml.responseText )
data.error(xml.responseText);
else
data.error(url +'\n' + e.message);
},
dataType: "html"
});
}
else
{
HttpLibrary.createScriptTag(data.url, data.success, data.error);
}
},
loadJavascript_MSAJAX : function(data)
{
if( HttpLibrary.browser.safari )
{
var params =
{
url: data.url,
success: function(content)
{
HttpLibrary.globalEval(content);
data.success(content);
},
error : data.error
};
HttpLibrary.httpGet_MSAJAX(params);
}
else
{
HttpLibrary.createScriptTag(data.url, data.success, data.error);
}
},
loadJavascript_Prototype : function(data)
{
if( HttpLibrary.browser.safari )
{
var params =
{
url: data.url,
success: function(content)
{
HttpLibrary.globalEval(content);
data.success(content);
},
error : data.error
};
HttpLibrary.httpGet_Prototype(params);
}
else
{
HttpLibrary.createScriptTag(data.url, data.success, data.error);
}
},
一个很酷的技巧是在全局上下文中执行下载的脚本。您可能会认为使用 eval
函数很容易做到。但它不起作用。eval
仅在当前作用域内执行调用。所以,您可能会认为,您可以调用 eval
在窗口对象的范围。不行,不起作用。唯一快速、跨浏览器的解决方案是这种方法。
globalEval : function(data)
{
var script = document.createElement("script");
script.type = "text/javascript";
if ( HttpLibrary.browser.msie )
script.text = data;
else
script.appendChild( document.createTextNode( data ) );
var head = HttpLibrary.getHead();
head.appendChild( script );
//head.removeChild( script );
}
它在 <head>
节点内创建一个 <script>
标签,然后将脚本文本传递给它。
实际应用示例
测试应用程序 向您展示了 ensure 的一些常见用法。例如,当单击一个按钮时,您需要调用一个 JavaScript 函数。该 JavaScript 函数(或可以轻松地)位于外部 JavaScript 文件中。以下是做法。
<input id="example1button" type="button" value="Click me"
onclick="
this.value='Loading...';
ensure({js:'Components/SomeJS.js'}, function(){
SomeJS();
this.value='Click me';
}, this)" />
因此,您可以看到 SomeJS
在 SomeJS.js 中可用。
下一个示例演示了如何按需在一个 DIV
中加载一些 HTML 片段。
<input id="example2button" type="button" value="Load Html, CSS on-demand"
onclick="
this.value='Loading...';
ensure({
html:'Components/HtmlSnippet.htm',
css:'Components/HtmlSnippet.css',
parent:'resultDiv'},
function(){
document.getElementById('clickMe').onclick = function()
{
alert('Clicked');
};
this.value='Load Html, CSS on-demand'
}, this)" />
单击按钮时,HtmlSnippet.html 和 HtmlSnippet.css 会被加载。HtmlSnippet.html 中的内容被注入到名为 resultDIV
的 DIV
中。当内容可用并成功注入后,会触发回调函数,在该函数中代码尝试挂接来自 HtmlSnippet.html 的按钮。
您可能错过了另一个很酷的方面,整个回调函数是在 <input>
按钮的上下文中触发的。ensure
的第三个参数确保了这一点。您看到,我将 this
作为作用域传递,即按钮本身。因此,当回调触发时,您仍然可以使用 this
来访问按钮。
测试应用程序中的第三个示例展示了如何加载多个 HTML、JavaScript 和 CSS 来提供两个 UI 效果 - 背景渐入和弹出对话框。
function showPopup()
{
ensure({
js: 'Components/BlockUI.js',
html: ['Components/BlockUI.html','Components/Popup.aspx'],
css: 'Components/Popup.css'
},
function()
{
BlockUI.show();
var popup = document.getElementById('Popup');
if( null == popup ) alert('Popup is not loaded!');
else popup.style.display = 'block';
document.getElementById('example3button').value = "Show me the UI";
});
}
此代码演示了如何一次性下载多个 JavaScript、HTML 和 CSS。
下载代码
从 CodePlex 下载最新的源代码。
结论
现在您可以确保在使用所需的 JavaScript、HTML、CSS 之前它们已经可用。确保在整个 Web 应用程序中使用 ensure 来确保快速下载时间,同时确保 UI 功能不受影响,从而确保更丰富的用户体验,确保快速的页面加载。