1. 程式人生 > >web優化之-mvc js動態合併 動態壓縮 去掉js重複引用 js快取 js延遲載入

web優化之-mvc js動態合併 動態壓縮 去掉js重複引用 js快取 js延遲載入

做web前段也有一段時間了,對於web中js檔案的載入有些體會想跟大家一起分享一下。

1.首先說說js檔案的合併和壓縮吧

為了便於集中式管理js的合併和壓縮我們建立一個Js.ashx檔案來專門處理合並壓縮,這裡我們借用Yahoo.Yui.Compressor工具來壓縮我們的js檔案

程式碼如下:

 public class Js : IHttpHandler
    {

        public void ProcessRequest(HttpContext context)
        {
            context.Response.ContentType = "text/javascript";
            HttpRequest request = context.Request;
            HttpResponse response = context.Response;
            if (!request.QueryString.AllKeys.Contains("href"))
            {
                response.Write("No Content");
            }
            else
            {
                string href = context.Request.QueryString["href"].Trim();
                string[] files = href.Split(new string[]{",",","},StringSplitOptions.RemoveEmptyEntries);
                foreach (string fileName in files)
                {
                    string filePath = context.Server.MapPath(fileName);
                    if (File.Exists(filePath))
                    {
                        string content = File.ReadAllText(filePath, Encoding.UTF8);
                        content = JavaScriptCompressor.Compress(content);
                        response.Write(content);
                    }
                    else
                    {
                        response.Write("\r\n未找到原始檔"+filePath+"\r\n");
                    }
                }
            }
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
當我們在瀏覽器訪問js時如:

http://localhost:58798/js.ashx?href=scripts/jquery.lazyload.js,scripts/jquery.validate.js

返回結果如圖:


但是在實際開發中很多專案為了追求js的合併和壓縮,開發很不友好把js的引用放在一個地方,寫了很長的一串啊,如上面js引用。

下面說說如何改善吧:

 public static class Extensions
    {
        const string jsFileKey = "JSFileKey";
        static string jshandlerUrl = string.Empty;
        public static string JsHandlerUrl
        {
            get
            {
                if (string.IsNullOrEmpty(jshandlerUrl))
                {
                    jshandlerUrl = ConfigurationManager.AppSettings["jsHandlerUrl"] ?? string.Empty;
                }
                return jshandlerUrl;
            }
        }

        public static void AppendJsFile(this HtmlHelper htmlHelper, string jsFile, int group = 1)
        {
            NameValueCollection jsFiles = null;
            if (htmlHelper.ViewContext.HttpContext.Items.Contains(jsFileKey))
            {
                jsFiles = htmlHelper.ViewContext.HttpContext.Items[jsFileKey] as NameValueCollection;
            }
            else
            {
                jsFiles = new NameValueCollection();
                htmlHelper.ViewContext.HttpContext.Items.Add(jsFileKey, jsFiles);
            }
            if (jsFiles.AllKeys.Contains(group.ToString()))
            {
                string fileUrl = jsFiles[group.ToString()];
                if (!fileUrl.Contains(jsFile))
                    jsFiles.Add(group.ToString(), jsFile);
            }
            else
            {
                jsFiles.Add(group.ToString(), jsFile);
            }

            htmlHelper.ViewContext.HttpContext.Items[jsFileKey] = jsFiles;
        }

        public static MvcHtmlString RenderJsFile(this HtmlHelper htmlHelper)
        {
            NameValueCollection jsFiles = null;
            StringBuilder content = new StringBuilder();
            if (htmlHelper.ViewContext.HttpContext.Items.Contains(jsFileKey))
            {
                jsFiles = htmlHelper.ViewContext.HttpContext.Items[jsFileKey] as NameValueCollection;
                List<string> jsKeys = jsFiles.AllKeys.OrderBy(x => x).ToList<string>();

                string jsFormat = "<script type=\"text/javascript\" src=\"{0}\"></script>";
                foreach (string key in jsKeys)
                {
                    string jsFile = jsFiles[key];
                    content.AppendFormat(jsFormat, JsHandlerUrl + jsFile);
                    //htmlHelper.ViewContext.HttpContext.Response.Write(string.Format(jsFormat, JsHandlerUrl + jsFile));
                }
            }
            return new MvcHtmlString(content.ToString());
        }
    }

這樣在開發的時候我們書寫程式碼就很方便了如:

    @{Html.AppendJsFile("Scripts/jquery.lazyload.js");}

這樣把所有的js檔案快取起來,最後在呼叫  @Html.RenderJsFile() 一次性全部輸出js。如:


至於為什麼這樣寫我就不多說了,這種思想在mvc中的RenderPartial和RenderAction都是可用的,如

重複的js引用也在Js.ashx去掉是不是很方便啊

最後生成的html程式碼:


2.下面我們來看看js的延遲載入,為了實現js延遲載入我們需要引用相關的js,在這裡我用的是lazyload.js,具體請參考http://blog.csdn.net/dz45693/article/details/7529584

延遲載入後的效果


在文件載入前只加載了一個2.2k的lazyload.js檔案,其他的js檔案都在ready後加載。

相應的html程式碼


3.下面再來看看js的快取吧,快取涉及到要做服務端和客戶端快取,客戶端快取說白了就是做304返回

修改後的程式碼:

  class CacheItem
        {
            public string Content { set; get; }
            public DateTime Expires { set; get; }
        }
        public void ProcessRequest(HttpContext context)
        {
            context.Response.ContentType = "text/javascript";
            HttpRequest request = context.Request;
            HttpResponse response = context.Response;
            if (!request.QueryString.AllKeys.Contains("href"))
            {
                response.Write("No Content");
            }
            else
            {
                string href = context.Request.QueryString["href"].Trim();
                string[] files = href.Split(new string[] { ",", "," }, StringSplitOptions.RemoveEmptyEntries);
                CacheItem item = null;
                object obj = HttpRuntime.Cache.Get(href);//服務端快取
                if (null == obj)
                {
                    StringBuilder allText = new StringBuilder();
                    foreach (string fileName in files)
                    {
                        string filePath = context.Server.MapPath(fileName);
                        if (File.Exists(filePath))
                        {
                            string content = File.ReadAllText(filePath, Encoding.UTF8);
                            content = JavaScriptCompressor.Compress(content);
                            //response.Write(content);
                            allText.Append(content);
                        }
                        else
                        {
                            // response.Write("\r\n未找到原始檔"+filePath+"\r\n");
                            allText.Append("\r\n未找到原始檔" + filePath + "\r\n");
                        }
                    }//end foreach
                     item = new CacheItem() { Content = allText.ToString(), Expires = DateTime.Now.AddHours(1) };
                     HttpRuntime.Cache.Insert(href, item, null, item.Expires, TimeSpan.Zero);
                }
                else
                {
                    item = obj as CacheItem;
                }
                if (request.Headers["If-Modified-Since"] != null && TimeSpan.FromTicks(item.Expires.Ticks - DateTime.Parse(request.Headers["If-Modified-Since"]).Ticks).Seconds < 100)
                {
                    response.StatusCode = 304;
                   // response.Headers.Add("Content-Encoding", "gzip");
                    response.StatusDescription = "Not Modified";  
                }
                else
                {
                    response.Write(item.Content);
                    SetClientCaching(response, DateTime.Now);
                }
            }//end else href
        }
        private void SetClientCaching(HttpResponse response, DateTime lastModified)
        {
            response.Cache.SetETag(lastModified.Ticks.ToString());
            response.Cache.SetLastModified(lastModified);
            //public 以指定響應能由客戶端和共享(代理)快取進行快取。  
            response.Cache.SetCacheability(HttpCacheability.Public);
            //是允許文件在被視為陳舊之前存在的最長絕對時間。  
            response.Cache.SetMaxAge(new TimeSpan(7, 0, 0, 0));
            //將快取過期從絕對時間設定為可調時間  
            response.Cache.SetSlidingExpiration(true);
        }  
執行效果如圖:

程式碼下載地址:http://download.csdn.net/detail/dz45693/4272920