首頁 >web前端 >js教程 >在ASP.NET MVC專案中使用RequireJS函式庫的用法範例_javascript技巧

在ASP.NET MVC專案中使用RequireJS函式庫的用法範例_javascript技巧

WBOY
WBOY原創
2016-05-16 15:15:331961瀏覽

RequireJS 是一個前端模組化開發的流行工具,本身就是一個Javascript的庫文件,即require.js 。
RequireJs的主要功能:

(1)實作js檔案的非同步加載,避免網頁失去回應;

(2)管理模組之間的依賴性,便於程式碼的編寫與維護。

前端模組化開發現在有很多的工具,大體上分為兩類,一類是像dojo之類的高大全,dojo v1.8之後已經內置了模組化開發組件;另一類是像require .js,sea.js 這種專心做模組化開發的工具。

從模組化劃分的規則來區分,主要分為AMD、CMD兩類,dojo、require.js 遵從前者,而sea.js 依循CMD規範。

require在單一頁面應用中能夠如魚得水,然而對於傳統的多頁面應用,使用require多少會有些困惑和不方便。

本文講解如何在ASP.NET MVC的結構中應用require,並且給出了壓縮腳本,實現半自動化壓縮。

將js程式碼分離
一般而言ASP.NET MVC的一個路由對應一個視圖,視圖的檔案結構可能如下:


Views
 |--Shared
 |--_layout.cshtml
 |--Home
 |--Index.cshtml
 |--Blog
 |--Create.cshtml
 |--Edit.cshtml
 |--Detail.cshtml
 |--Index.cshtml

這裡假設_layout.cshtml是所有頁面共享的。一般情況下,我們會在_layout中引用公共的js類別函式庫,例如jQuery,bootstrap等,這樣的話其他的頁面就不需要對這些類別庫再引用一遍,提高了編碼的效率。然而,不同的頁面終究會依賴不同的js,尤其是實現頁面本身功能的自訂的js,這樣我們就必須在其他頁面中再引用特殊的js,甚至將js直接寫在頁面中,例如下面的程式碼常會出現在View:

<script type="text/javascript">
 $(function(){...});
</script>

這樣會導致頁面比較混亂,而且頁面<script>標籤中程式碼不能被瀏覽器緩存,增加了頁面程式碼的長度。更重要的缺陷是,諸如jQuery之類的類別庫會在載入到頁面後執行匿名函數,這需要一些時間,而如果有些頁面根本不需要jQuery的話,只要頁面把_layout當作佈局頁面,那麼jQuery的初始化程式碼將不可避免的執行,這是一種浪費。事實上,javascript的模組化載入的想法就是為了解決這些問題的。 </script>

接下來我們來用require規劃我們的js,建構諸如下面結構的js目錄


js
|--app
 |--home.index.js
 |--blog.create.js
 |--blog.edit.js
 |--blog.detail.js
 |--blog.index.js
|--jquery.js
|--bootstrap.js
|--underscore.js
|--jquery.ui.js
|--jquery.customplugin.js
|--config.js
|--require.js

把公共的類別庫層級的js模組直接放在js目錄下,而把頁面層級的js放在一個app的子目錄下。注意,在app中,每個頁面一個js文件,這意味著我們需要把頁面各自的js提取出來,雖然這樣增加了結構複雜度,但是避免了在頁面中隨手寫<script>標籤的陋習。另外,在js目錄下的公共函式庫,除了第三方的函式庫,還包含自己開發的函式庫,還有一個叫config.js的文件,這個文件很關鍵,稍後會說到。 </script>

然後,我們可以刪除_layout中所有的js引用,並使用@RenderSection的命令要求子頁面提供js引用,_layout.cshtml:

<head>
...
@RenderSection("require_js_module", false)
...
</head>

這樣對js的需求就下放到每個view頁面中了,根據require的用法,我們需要在各個子View中引用require.js,並指定主模組,而這些主模組就是上面app目錄下的一個個js

@section require_js_module{
 <script src="@Url.Content("~/js/require.js")" data-main="@Url.Content("~/js/app/home.index.js")" ></script>
}

所有的js程式碼都會寫到app下的js中,這樣規範了js,使得頁面更乾淨,更為重要的是這些js還可以經過壓縮,以及被瀏覽器緩存等,進一步提高執行效率

公用的config
我們知道主模組除了使用require方法外,經常需要透過require.config來配置其他模組的路徑,甚至需要shim,例如下面的程式碼經常會出現在主模組的開頭:

require.config({
 paths: {
 "jquery": "lib/jquery.min",
 "underscore": "lib/underscore.min",
 "backbone": "lib/backbone.min"
 },
 shim: {
 'underscore':{
  exports: '_'
 },
 'backbone': {
  deps: ['underscore', 'jquery'],
  exports: 'Backbone'
 }
 }
});

對於單一頁面應用程式來說,主模組往往只有一個,所以上面的程式碼寫一遍也就OK了。但是,在多頁面的情況下,主模組有多個,每個主模組都要包含這樣的程式碼,豈不是很不科學?於是,希望有一個統一配置的地方,但是應該如何來寫呢?我們想到,將這些配置作為一個模組config.js,讓其他的主模組對這個模組產生依賴就可以了,例如下面的config.js:
requirejs.config({
 paths: {
 "jquery": "/js/jquery.min",
 "bootstrap": "/js/bootstrap"
 },
 shim: {
 'bootstrap': {
  deps: ['jquery'],
  exports: "jQuery.fn.popover"
 }
 }
});

config.js的寫法沒有什麼特別的,接下來只要在home.index.js引用

require(['../config','jquery', 'bootstrap'], function () {
 //main module code here

});

不過這樣寫還是不對的,因為,被主模組依賴的模組(這裡的config,jquery,bootstrap),在載入的時候,載入順序是不確定的,但是又需要config模組在其他模組之前加載,怎麼辦呢?一個折衷的方案是修改home.index.js,成為以下程式碼:

require(['../config'], function () {
 require(['home.index2']);
})
, define("home.index2", ['jquery', 'bootstrap'], function () {
 //main module code here
})

使用一个命名的模块home.index2作为过渡,在主模块中手动require,这样可以保证config在主模块执行之前加载,也就使得home.index2在加载的时候已经加载了config了。

压缩
require提供一个压缩工具,用于压缩和合并js,详情请移步至http://requirejs.org/docs/optimization.html。简单的说,require提供一个叫r.js的文件,通过本地的node程序(Node.js),执行这个r.js并传入一些参数,即可自动分析模块互相之间的依赖,以达到合并和压缩的目的。同样的,这对于单页面应用来说是容易的,因为主模块只有一个,但是对于多页面又如何做呢?好在这个压缩工具支持用一个配置文件来指导压缩,这样的话,我们可以编写下面的配置脚本build.js:

var build = {
 appDir: '../js',
 baseUrl: '.',
 dir: '../js-built',
 mainConfigFile: '../js/config.js',
 modules: [
 //First set up the common build layer.
 {
  //module names are relative to baseUrl
  name: 'config',
  //List common dependencies here. Only need to list
  //top level dependencies, "include" will find
  //nested dependencies.
  include: ["bootstrap", "config","jquery"]
 },
 //Now set up a build layer for each page, but exclude
 //the common one. "exclude" will exclude nested
 //the nested, built dependencies from "common". Any
 //"exclude" that includes built modules should be
 //listed before the build layer that wants to exclude it.
 //"include" the appropriate "app/main*" module since by default
 //it will not get added to the build since it is loaded by a nested
 //require in the page*.js files.
 {
 name:"app/home.index",
 exclude:["config"]
 },
 {
 name:"app/blog.create",
 exclude:["config"]
 },
 ...
 ]
}

通过这个命令来执行压缩,压缩的结果将被保存到js-build目录:

node.exe r.js -o build.js

build.js脚本实际上是一个js对象,我们将config加入公共模块,而在各个主模块中将其排除。这样,所有的公共库包括config将压缩成一个js,而主模块又不会包含多余的config。这样可想而知,每个页面在加载时最多只会下载两个js,而且公共模块的代码会“按需执行”。

执行上面的脚本压缩,需要安装有node。可以在从这里下载。

自动脚本
但是,随着主模块的增加,需要随时跟踪和修改这个build文件,这也是很麻烦的。于是,笔者基于node.js开发了一个叫build-build.js的脚本,用来根据目录结构自动生成build.js:

fs = require('fs');
var target_build = process.argv[2];
//console.log(__filename);
var pwd = __dirname;
var js_path = pwd.substring(0,pwd.lastIndexOf('\\')) + '\\js';
console.log('js path : ' + js_path);
var app_path = js_path + '\\app';
console.log('js app path : ' +app_path);

var app_modules = [];
var global_modules = [];

//build json object
var build = {
 appDir: '../js',
 baseUrl: '.',
 dir: '../js-built',
 modules: [
 //First set up the common build layer.
 {
  //module names are relative to baseUrl
  name: 'config',
  //List common dependencies here. Only need to list
  //top level dependencies, "include" will find
  //nested dependencies.
  include: []
 }
 ]
}

fs.readdir(app_path,function (err,files) {
 // body...
 if (err) throw err;
 for(var i in files){
 //put module in app_modules
 var dotindex = files[i].lastIndexOf('.');
 if(dotindex >= 0){
  var extension = files[i].substring(dotindex+1,files[i].length);
  if(extension == 'js'){
  app_modules.push({
   name: 'app/' + files[i].substring(0,dotindex),
   exclude: ['config']
  });
  }
 }
 }

 for(var j in app_modules){
 build.modules.push(app_modules[j]);
 }
 
 fs.readdir(js_path,function (err,files){
 if (err) throw err;
 for(var i in files){
  //put module in app_modules
  var dotindex = files[i].lastIndexOf('.');
  if(dotindex >= 0){
  var extension = files[i].substring(dotindex+1,files[i].length);
  if(extension == 'js'){
   global_modules.push(files[i].substring(0,dotindex));
  }
  } 
 }

 build.modules[0].include = global_modules;
 //console.log(build);
 var t = pwd + '\\' + target_build;
 console.log(t);
 var fd = fs.openSync(t, 'w');
 fs.closeSync(fd);
 var json = JSON.stringify(build);
 fs.writeFileSync(t, json);
 });
});

这里的代码并不复杂,主要是遍历目录,生成对象,最后将对象序列化为build.js。读者可以自行阅读并修改。最后,编写一个bat,完成一键压缩功能,build.bat:

@echo off
set PWD=%~p0
set PWD=%PWD:\=/%
cd "D:\node"
node.exe %PWD%build-build.js build.js
node.exe %PWD%r.js -o %PWD%build.js
cd %~dp0

这样,我们就简单实现了一个方便的多页面require方案,最后项目目录可能是这样的:

Views
 |--Shared
 |--_layout.cshtml
 |--Home
 |--Index.cshtml
 |--Blog
 |--Create.cshtml
 |--Edit.cshtml
 |--Detail.cshtml
 |--Index.cshtml

build
|--build.js
|--r.js
|--build-build.js
|--build.bat

js
|--app
 |--home.index.js
 |--blog.create.js
 |--blog.edit.js
 |--blog.detail.js
 |--blog.index.js
|--jquery.js
|--bootstrap.js
|--underscore.js
|--jquery.ui.js
|--jquery.customplugin.js
|--config.js
|--require.js


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn