可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I typically put all the JavaScript scripts into one file e.g. scripts.js
(the less HTTP request, the better). So, as expected, some scripts are needed for some pages, some aren't.
To target a specific page, I use something like:
if ($("body#share").length > 0) {
// Place the logic pertaining to the page with ID 'share' here...
}
// The file / script continues...
Other or better suggestions? Thanks!
Clarification: I was not looking for the pros / cons between consolidating multiple JS files into one big file and keeping multiple separate JS files. The answer for this is surely 'depends on the situation' (we know that). My question is, assuming all my JS logic is placed into one big file, how do I make a particular (chunk of) script runs only when the corresponding page is loaded? One way I used to do is using if ($('#id-of-target-element')) { /* run the script */}
; is there a better way?
回答1:
I like Paul Irish's approach... you don't have to follow it exactly, but the general idea is a very solid one.
It might look something like this for your example
Html
<body id="share">
Your page specific javascript
YourNamespace = {
share : {
init : function(){
// Place the logic pertaining to the page with ID 'share' here...
}
}
}
Paul Irish's Javascript that makes the magic happen
UTIL = {
fire : function(func,funcname, args){
var namespace = YourNamespace; // indicate your obj literal namespace here
funcname = (funcname === undefined) ? 'init' : funcname;
if (func !== '' && namespace[func] && typeof namespace[func][funcname] == 'function'){
namespace[func][funcname](args);
}
},
loadEvents : function(){
var bodyId = document.body.id;
// hit up common first.
UTIL.fire('common');
// do all the classes too.
$.each(document.body.className.split(/\s+/),function(i,classnm){
UTIL.fire(classnm);
UTIL.fire(classnm,bodyId);
});
UTIL.fire('common','finalize');
}
};
// kick it all off here
$(document).ready(UTIL.loadEvents);
So the line you see directly above will kick off the following
YourNamespace.common.init()
YourNamespace.share.init()
YourNamespace.common.finalize()
Have a read of his blog post and a few of the variations linked from it.
回答2:
Similar questions have been already asked and the correct answer was and always will be
It depends on the situation.
However, if your concern is about minimizing the round-trip time (RTT) then it is certain that
Combining external scripts into as few files as possible cuts down on
RTTs and delays in downloading other resources.
It is good to keep it as few as possible, but you don't necessarily have to keep it into one file strictly.
Let's take a look at why it is so.
While partitioning code into modular software components is a good
engineering practice, importing modules into an HTML page one at a
time can drastically increase page load time. First, for clients with
an empty cache, the browser must issue an HTTP request for each
resource, and incur the associated round trip times. Secondly, most
browsers prevent the rest of the page from from being loaded while a
JavaScript file is being downloaded and parsed.
These images show it more clearly why combining a number of JavaScript files into fewer output files can dramatically reduce latency:
All files are downloaded serially, and take a total of 4.46 seconds to complete.
After collapsing the 13 js files into 2 files:
The same 729 kilobytes now take only 1.87 seconds to download
Edit after Clarification given by Siku-Siku.Com:
Sorry! I totally misunderstood your question. I don't know of any better way for making a particular (chunk of) script run only when the corresponding page is loaded. I think your way is good enough.
回答3:
Another option is that you could have, within the HTML code
<script>
callYourJSFunctionHere(); /*Things you would want to happen on Page load*/
</script>
This will follow the normal flow of the page. Therefore if you are playing with elements on the page, you will need to place this <script>
portion at the bottom of the page, after all the elements have been loaded by the browser.
I'm not 100% sure this is more efficient then the current solution you have, but on the other hand, it will be telling someone looking at your HTML page what JavaScript will be run when the page loads. This might be helpful in terms of maintenance down the road.... no so much a guessing game as to what script runs when the page loads.
Hope this helps!
回答4:
Your suggestion seems OK. I would however use an HTML 5 data- attribute to tag every page like this:
<body data-title="my_page_title">
You can then write conditional javascript code by checking this property (jQuery 1.4.3 onwards):
if ($("body").data("title") === "my_page_title") {
// Place the logic pertaining to the page with title 'my_page_title' here...
}
This allows you to systematically group all code for a certain page in a sensible way
回答5:
Ok. Lets hope, code example will say it better, than wall of text:
your one-and-only.js
file:
var MY_SITE = {
// you have one namespace-object where you hold all your stuff
main_page: {
// inside you have smaller chunks, for specific pages or widgets
// this one is for main page
_init: function () {
...
},
...
showSplashScreen: function () {
...
}
},
...
// but there are other pages
contacts: { ... },
// or widgets
cool_menu: { ... },
// and maybe you want to put common functions into dedicated block
global_utilities: { ... },
// and of course - the loader
_start: function () {
// here we simply call one _init of the page module that was specified
this[PAGE_TYPE]._init();
// and more tricky stuff - we can search the page for elements
// that contain special data-attribute, collect them
var widgets = $('[data-our-widget]').each().getAttributeValue( 'data-or-widget' );
// and then _init each of those widgets just like we did with main page
widgets.forEach( function (v) {
this[v]._init();
}
}
};
document.on('ready', MY_SITE.start); // here we assume some random js framework
your <script>
tags on the page:
<script>var PAGE_TYPE = 'main_page';</script>
<script src="one-and-only.js" />
your 'magic' elements on the page:
<div id="whatever" data-our-widget="cool_menu"> ... </div>
Disclaimer: This is high level overview! Implementation details may and should vary, depending on your needs.
回答6:
Your question is asking how to only load chunks of your scripts based upon a particular page. However, since your stated goal is minimizing http requests (and traffic, I assume), I think my unique (complicated) implementation is at least relevant to this question, if not a good alternative/complement to your approach.
I use this in an embedded application where I combine all javascript files into one large one for reducing http requests. I had to figure out a caching method as well. A 120kb javascript file in addition to several decently-sized images take a while to load when using a barebones 50kb webserver.
First off, I use YUI Compressor to compress my javascript files, as that's an easy way to save bandwidth.
A cool "feature" I discovered was if you link to anything (css, js, img) like the following, you'll simply load the file and ignore the parameters past the '?' in 'src':
<script type="text/Javascript" src="js/all.js?2382a97f099f42cc43c1b616cd24f281"></script>
Now, that seemingly random jumble of numbers and letters is actually the md5 checksum of the javascript file! I then modified my server's response header for the correlated extensions (css, js, img, etc) to never expire. This header is not sent for html files, as we always want the latest versions of those.
Cache-Control:max-age=290304000
This means clients will load the javascript file once and only once until the checksum changes. Caching works by looking up the entire request, including file parameters.
I'm actually compiling my html/js/css/image files in with C code, so I have a perl script automatically insert those md5 checksums into the html files before compiling. This obviously won't work in your case, but you can use any number of ways to handle it, as long as you have a server-side language to aid you. For example, you can have php calculate the checksum on-the-fly, or you can have php store the checksums in a database or flat file to easily look them up. In order to flag php to recalculate the checksum, you could check the filetime, or delete the database entries/flat file.
I realize this is a lot of upfront work and might not be what you're after, but it works wonderfully for me. The performance gains for me are incredible. The first page load is awfully slow (8.5 seconds), but after that, all your scripts, css, and images are all cached now. New page loads within the same application drop to ~300ms.
回答7:
As you've rightly mentioned, the idea of splitting / combining files really does depend on the situation.
I don't think there can be a better solution for what you're doing, as I have seen many big websites implement a similar strategy albeit more for CSS than js.
Eg (An excerpt from facebook):
<body class="hasLeftCol home fbx hasSlimHeader safari4 win Locale_en_US defaultScrollbar">
There is one more great advantage you can reap out of this. You can selectively apply some JS code into groups of pages using the class attribute.
ie.
if $('body.pagesThatHaveThisClass').length > 0)
{.... code ...}
if $('body#singlePageWithThisId').length > 0)
{.... code ...}
Else, there is a technique that many JS/jQ based menus use to automatically give a different style to the current page. You can match the window.location to specific a address.
ie.
if (window.top.location.href == 'http://domain.com/mypage1.html')
{ .... code ....}
or even
switch (window.top.location.href)
{
case 'http://domain.com/mypage1.html' :
{ .... code ....}
case 'http://domain.com/mypage2.html' :
{ .... code ....}
}
I would still prefer your technique as you can group code for multiple pages together. The only reason I can think of for using the second method is when the JS people don't have access to the HTML of the page to change the class / id etc.
Hope this is what you were looking for!
回答8:
How about testing window.location
? That seems the obvious way for me.
Or as you already seem to do, test for the required elements to be there. That is a rather robust method, I see nothing wrong with that.
回答9:
This is going to be a high level overview, but I recommend using Backbone to organize the logic for each "view" (or page.) I use a root element and change the class of that element to switch between the other views (or states) of the layout. You can then organize the secondary styles by class and keep a primary style under the ID.
HTML
<div id="layout" class="default">
<!-- PUT DEFAULT HTML IN HERE -->
</div>
<script id="shareTemplate" type="text/html">
<!-- PUT SHARE HTML IN HERE -->
</script>
CSS
#layout {
/* primary style */
}
#layout.share {
/* secondary style */
}
JavaScript
window.LayoutView = Backbone.View.extend({
el: "#layout",
shareTemplate: _.template($("#shareTemplate").html()),
initialize: function() {
this.render(shareTemplate, data);
this.el.className = "share";
},
render: function(template, data) {
$(this.el).html(template(data));
return this;
}
});
window.Layout = new LayoutView;
回答10:
Based on the assumption that all of the different pages on your site is categorically or at least logically grouped into different "view models", with a different view model for a different type of page (e.g. "search results", "blogposts", "front page", "single blogpost", etcetera), you could have a single javascript file that contains the scripts in common for all of your site, and one specific for each view model (if you deem it necessary).
You could then have the server insert the name of the view model into, for instance, a HTML data-tag, for later use by JavaScript, and an additional script tag after the main script library.
For instance:
<head>
<!--
Two JavaScript files, one for the common functionality,
and one for the view model specfic
-->
<script type="text/javascript" src="path/to/scriptslib.js"></script>
<script type="text/javascript" src="path/to/viewmodel.front_page.js"></script>
</head>
<body data-view-model="front_page">
...
</body>
In scriptslib.js
:
var
GlobalSettings = {},
ViewModel = {};
$(function () {
// Save the view model in a variable when the body's been loaded
GlobalSettings.viewModel = $("body").attr("data-view-model");
// Assuming that the viewmodel JavaScript file has been loaded, call a method that it'll have by convention here
ViewModel.initialize();
// Additionally, you can later test for specific view models and perform necessary actions, if needed
if (viewModel === "front_page") {
// ...
}
});
Then, in viewmodel.front_page.js
(or any other view model file):
ViewModel.FrontPage = {
// data and methods specific for this view model
}
ViewModel.initialize = function () {
// do stuff specific for this view model
this.FrontPage.someFunction();
};
Note, that the way I've described the approach here is for sites that rely on page reloads for shifting between pages. If you were using Ajax to shift between pages, you'd want JavaScript to dynamically load the new view models and execute them.
Hope this could work for you ;)
回答11:
You can follow your approach, but suppose your "cocktail" javascript file has 20k lines of code, and you include it in all you html files, even those that show a "404" or "500", wouldn't it use more unnecessary bandwidth?
I opt for using different js for each HTML page and whatever is used across multiple pages dump it in a "utils.js" or whatever you want to call it, example
\www
\pages
\index.html
\dashboard.html
\account.html
\ ...
\scripts
\index.js <- for index.html
\dashboard.js <- for dashboard.html
\account.js <- for account.html
\ ...
\utils.js|shared.js|sharedlib.js <- used by all pages
so basically it will result in:
- 1 request for the html
- 1 for the page script
- 1 for the shared script, if used
- n for css's, but I assume is 1
This is just my two cents..