<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                <h2 id="10.1">Underscore.js</h2> ## 概述 [Underscore.js](http://underscorejs.org/)是一個很精干的庫,壓縮后只有4KB。它提供了幾十種函數式編程的方法,彌補了標準庫的不足,大大方便了JavaScript的編程。MVC框架Backbone.js就將這個庫作為自己的工具庫。除了可以在瀏覽器環境使用,Underscore.js還可以用于Node.js。 Underscore.js定義了一個下劃線(_)對象,函數庫的所有方法都屬于這個對象。這些方法大致上可以分成:集合(collection)、數組(array)、函數(function)、對象(object)和工具(utility)五大類。 ## 集合相關方法 Javascript語言的數據集合,包括兩種結構:數組和對象。以下的方法同時適用于這兩種結構。 ### 集合處理 數組處理指的是對數組元素進行加工。 **(1)map** map方法對集合的每個成員依次進行某種操作,將返回的值依次存入一個新的數組。 ```javascript _.map([1, 2, 3], function(num){ return num * 3; }); // [3, 6, 9] _.map({one : 1, two : 2, three : 3}, function(num, key){ return num * 3; }); // [3, 6, 9] ``` **(2)each** each方法與map類似,依次對數組所有元素進行某種操作,不返回任何值。 ```javascript _.each([1, 2, 3], alert); _.each({one : 1, two : 2, three : 3}, alert); ``` **(3)reduce** reduce方法依次對集合的每個成員進行某種操作,然后將操作結果累計在某一個初始值之上,全部操作結束之后,返回累計的值。該方法接受三個參數。第一個參數是被處理的集合,第二個參數是對每個成員進行操作的函數,第三個參數是累計用的變量。 ```javascript _.reduce([1, 2, 3], function(memo, num){ return memo + num; }, 0); // 6 ``` reduce方法的第二個參數是操作函數,它本身又接受兩個參數,第一個是累計用的變量,第二個是集合每個成員的值。 **(4)reduceRight** reduceRight是逆向的reduce,表示從集合的最后一個元素向前進行處理。 ```javascript var list = [[0, 1], [2, 3], [4, 5]]; var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []); // [4, 5, 2, 3, 0, 1] ``` **(5)shuffle** shuffle方法返回一個打亂次序的集合。 ```javascript _.shuffle([1, 2, 3, 4, 5, 6]); // [4, 1, 6, 3, 5, 2] ``` **(6)invoke** invoke方法對集合的每個成員執行指定的操作。 ```javascript _.invoke([[5, 1, 7], [3, 2, 1]], 'sort') // [[1, 5, 7], [1, 2, 3]] ``` **(7)sortBy** sortBy方法根據處理函數的返回值,返回一個排序后的集合,以升序排列。 ```javascript _.sortBy([1, 2, 3, 4, 5, 6], function(num){ return Math.sin(num); }); // [5, 4, 6, 3, 1, 2] ``` **(8)indexBy** indexBy方法返回一個對象,根據指定鍵名,對集合生成一個索引。 ```javascript var person = [{name: 'John', age: 40}, {name: 'larry', age: 50}, {name: 'curly', age: 60}]; _.indexBy(person, 'age'); // { "50": {name: 'larry', age: 50}, "60": {name: 'curly', age: 60} } ``` ### 集合特征 Underscore.js提供了一系列方法,判斷數組元素的特征。這些方法都返回一個布爾值,表示是否滿足條件。 **(1)every** every方法判斷數組的所有元素是否都滿足某個條件。如果都滿足則返回true,否則返回false。 ```javascript _.every([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); // false ``` **(2)some** some方法則是只要有一個元素滿足,就返回true,否則返回false。 ```javascript _.some([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); // true _.some([null, 0, 'yes', false]) // true ``` **(3)size** size方法返回集合的成員數量。 ```javascript _.size({one : 1, two : 2, three : 3}); // 3 ``` **(4)sample** sample方法用于從集合中隨機取樣。 ```javascript _.sample([1, 2, 3, 4, 5, 6]) // 4 ``` ### 集合過濾 Underscore.js提供了一系列方法,用于過濾數組,找到符合要求的成員。 **(1)filter** filter方法依次對集合的每個成員進行某種操作,只返回操作結果為true的成員。 ```javascript _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); // [2, 4, 6] ``` **(2)reject** reject方法只返回操作結果為false的成員。 ```javascript _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); // [1, 3, 5] ``` **(3)find** find方法依次對集合的每個成員進行某種操作,返回第一個操作結果為true的成員。如果所有成員的操作結果都為false,則返回undefined。 ```javascript _.find([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); // 2 ``` **(4)contains** contains方法表示如果某個值在數組內,則返回true,否則返回false。 ```javascript _.contains([1, 2, 3], 3); // true ``` **(5)countBy** countBy方法依次對集合的每個成員進行某種操作,將操作結果相同的成員算作一類,最后返回一個對象,表明每種操作結果對應的成員數量。 ```javascript _.countBy([1, 2, 3, 4, 5], function(num) { return num % 2 == 0 ? 'even' : 'odd'; }); // {odd: 3, even: 2} ``` **(6)where** where方法檢查集合中的每個值,返回一個數組,其中的每個成員都包含指定的鍵值對。 ```javascript _.where(listOfPlays, {author: "Shakespeare", year: 1611}); // [{title: "Cymbeline", author: "Shakespeare", year: 1611}, // {title: "The Tempest", author: "Shakespeare", year: 1611}] ``` **(7)max,min** max方法返回集合中的最大值。如果提供一個處理函數,則該函數的返回值用作排名標準。 ```javascript var person = [{name: 'John', age: 40}, {name: 'larry', age: 50}, {name: 'curly', age: 60}]; _.max(person, function(per){ return per.age; }); // {name: 'curly', age: 60}; ``` min方法返回集合中的最小值。如果提供一個處理函數,則該函數的返回值用作排名標準。 ```javascript var numbers = [10, 5, 100, 2, 1000]; _.min(numbers) // 2 ``` ## 對象相關方法 **(1)toArray** toArray方法將對象轉為數組,只包含對象成員的值。典型應用是將對類似數組的對象轉為真正的數組。 ```javascript _.toArray({a:0,b:1,c:2}); // [0, 1, 2] ``` **(2)pluck** pluck方法將多個對象的某一個屬性的值,提取成一個數組。 ```javascript var person = [{name: 'John', age: 40}, {name: 'larry', age: 50}, {name: 'curly', age: 60}]; _.pluck(person, 'name'); // ["moe", "larry", "curly"] ``` ## 與函數相關的方法 ### 綁定運行環境和參數 在不同的運行環境下,JavaScript函數內部的變量所在的上下文是不同的。這種特性會給程序帶來不確定性,為了解決這個問題,Underscore.js提供了兩個方法,用來給函數綁定上下文。 **(1)bind方法** 該方法綁定函數運行時的上下文,返回一個新函數。 ```javascript var o = { p: 2, m: function (){console.log(this.p);} }; o.m() // 2 _.bind(o.m,{p:1})() // 1 ``` 上面代碼將o.m方法綁定到一個新的對象上面。 除了前兩個參數以外,bind方法還可以接受更多參數,它們表示函數方法運行時所需的參數。 ```javascript var add = function(n1,n2,n3) { console.log(this.sum + n1 + n2 + n3); }; _.bind(add, {sum:1}, 1, 1, 1)() // 4 ``` 上面代碼中bind方法有5個參數,最后那三個是給定add方法的運行參數,所以運行結果為4。 **(2)bindall方法** 該方法可以一次將多個方法,綁定在某個對象上面。 ```javascript var o = { p1 : '123', p2 : '456', m1 : function() { console.log(this.p1); }, m2 : function() { console.log(this.p2); }, }; _.bindAll(o, 'm1', 'm2'); ``` 上面代碼一次性將兩個方法(m1和m2)綁定在o對象上面。 **(3)partial方法** 除了綁定上下文,Underscore.js還允許綁定參數。partial方法將函數與某個參數綁定,然后作為一個新函數返回。 ```javascript var add = function(a, b) { return a + b; }; add5 = _.partial(add, 5); add5(10); // 15 ``` **(4)wrap方法** 該方法將一個函數作為參數,傳入另一個函數,最終返回前者的一個新版本。 ```javascript var hello = function(name) { return "hello: " + name; }; hello = _.wrap(hello, function(func) { return "before, " + func("moe") + ", after"; }); hello(); // 'before, hello: moe, after' ``` 上面代碼先定義hello函數,然后將hello傳入一個匿名定義,返回一個新版本的hello函數。 **(5)compose方法** 該方法接受一系列函數作為參數,由后向前依次運行,上一個函數的運行結果,作為后一個函數的運行參數。也就是說,將f(g(),h())的形式轉化為f(g(h()))。 ```javascript var greet = function(name){ return "hi: " + name; }; var exclaim = function(statement){ return statement + "!"; }; var welcome = _.compose(exclaim, greet); welcome('moe'); // 'hi: moe!' ``` 上面代碼調用welcome時,先運行greet函數,再運行exclaim函數。并且,greet函數的運行結果是exclaim函數運行時的參數。 ### 函數運行控制 Underscore.js允許對函數運行行為進行控制。 **(1)memoize方法** 該方法緩存一個函數針對某個參數的運行結果。 ```javascript var fibonacci = _.memoize(function(n) { return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); }); ``` **(2)delay方法** 該方法可以將函數推遲指定的時間再運行。 ```javascript var log = _.bind(console.log, console); _.delay(log, 1000, 'logged later'); // 'logged later' ``` 上面代碼推遲1000毫秒,再運行console.log方法,并且指定參數為“logged later”。 **(3)defer方法** 該方法可以將函數推遲到待運行的任務數為0時再運行,類似于setTimeout推遲0秒運行的效果。 ```javascript _.defer(function(){ alert('deferred'); }); ``` **(4)throttle方法** 該方法返回一個函數的新版本。連續調用這個新版本的函數時,必須等待一定時間才會觸發下一次執行。 ```javascript // 返回updatePosition函數的新版本 var throttled = _.throttle(updatePosition, 100); // 新版本的函數每過100毫秒才會觸發一次 $(window).scroll(throttled); ``` **(5)debounce方法** 該方法返回的新函數有調用的時間限制,每次調用必須與上一次調用間隔一定的時間,否則就無效。它的典型應用是防止用戶雙擊某個按鈕,導致兩次提交表單。 ```javascript $("button").on("click", _.debounce(submitForm, 1000, true)); ``` 上面代碼表示click事件發生后,調用函數submitForm的新版本。該版本的兩次運行時間,必須間隔1000毫秒以上,否則第二次調用無效。最后那個參數true,表示click事件發生后,立刻觸發第一次submitForm函數,否則就是等1000毫秒再觸發。 **(6)once方法** 該方法返回一個只能運行一次的新函數。該方法主要用于對象的初始化。 ```javascript var initialize = _.once(createApplication); initialize(); initialize(); // Application只被創造一次 ``` **(7)after方法** 該方法返回的新版本函數,只有在被調用一定次數后才會運行,主要用于確認一組操作全部完成后,再做出反應。 ```javascript var renderNotes = _.after(notes.length, render); _.each(notes, function(note) { note.asyncSave({success: renderNotes}); }); ``` 上面代碼表示,函數renderNotes是函數render的新版本,只有調用notes.length次以后才會運行。所以,后面就可以放心地等到notes的每個成員都處理完,才會運行一次renderNotes。 ## 工具方法 ### 鏈式操作 Underscore.js允許將多個操作寫成鏈式的形式。 ```javascript _.(users) .filter(function(user) { return user.name === name }) .sortBy(function(user) { return user.karma }) .first() .value() ``` ### template 該方法用于編譯HTML模板。它接受三個參數。 ```javascript _.template(templateString, [data], [settings]) ``` 三個參數的含義如下: - templateString:模板字符串 - data:輸入模板的數據 - settings:設置 **(1)templateString** 模板字符串templateString就是普通的HTML語言,其中的變量使用<%= … %>的形式插入;data對象負責提供變量的值。 ```javascript var txt = "<h2><%= word %></h2>"; _.template(txt, {word : "Hello World"}) // "<h2>Hello World</h2>" ``` 如果變量的值包含五個特殊字符(& < > " ' /),就需要用<%- ... %>轉義。 ```javascript var txt = "<h2><%- word %></h2>"; _.template(txt, {word : "H & W"}) // <h2>H &amp; W</h2> ``` JavaScript命令可以采用<% … %>的形式插入。下面是判斷語句的例子。 ```javascript var txt = "<% var i = 0; if (i<1){ %>" + "<%= word %>" + "<% } %>"; _.template(txt, {word : "Hello World"}) // Hello World ``` 常見的用法還有循環語句。 ```javascript var list = "<% _.each(people, function(name) { %> <li><%= name %></li> <% }); %>"; _.template(list, {people : ['moe', 'curly', 'larry']}); // "<li>moe</li><li>curly</li><li>larry</li>" ``` 如果template方法只有第一個參數templateString,省略第二個參數,那么會返回一個函數,以后可以向這個函數輸入數據。 ```javascript var t1 = _.template("Hello <%=user%>!"); t1({ user: "<Jane>" }) // 'Hello <Jane>!' ``` ** (2)data ** templateString中的所有變量,在內部都是obj對象的屬性,而obj對象就是指第二個參數data對象。下面兩句語句是等同的。 ```javascript _.template("Hello <%=user%>!", { user: "<Jane>" }) _.template("Hello <%=obj.user%>!", { user: "<Jane>" }) ``` 如果要改變obj這個對象的名字,需要在第三個參數中設定。 ```javascript _.template("<%if (data.title) { %>Title: <%= title %><% } %>", null, { variable: "data" }); ``` 因為template在變量替換時,內部使用with語句,所以上面這樣的做法,運行速度會比較快。 <h2 id="10.2">Modernizr</h2> ## 概述 隨著HTML5和CSS3加入越來越多的模塊,檢查各種瀏覽器是否支持這些模塊,成了一大難題。Modernizr就是用來解決這個問題的一個JavaScript庫。 首先,從modernizr.com下載這個庫。下載的時候,可以選擇所需要的模塊。然后,將它插入HTML頁面的頭部,放在head標簽之中。 ```html <!DOCTYPE html> <html class="no-js" lang="en"> <head> <meta charset="utf-8"> <script src="js/modernizr.js"></script> </head> </html> ``` ## CSS的新增class 使用Modernizr以后,首先會把html元素的class替換掉。以chrome瀏覽器為例,新增的class大概是下面的樣子。 ```html <html class="js no-touch postmessage history multiplebgs boxshadow opacity cssanimations csscolumns cssgradients csstransforms csstransitions fontface localstorage sessionstorage svg inlinesvg blobbuilder blob bloburls download formdata"> ``` IE 7則是這樣: ```html <html class="js no-touch postmessage no-history no-multiplebgs no-boxshadow no-opacity no-cssanimations no-csscolumns no-cssgradients no-csstransforms no-csstransitions fontface localstorage sessionstorage no-svg no-inlinesvg wf-loading no-blobbuilder no-blob no-bloburls no-download no-formdata"> ``` 然后,就可以針對不同的CSS class,指定不同的樣式。 ```css .button { background: #000; opacity: 0.75; } .no-opacity .button { background: #444; } ``` ## JavaScript偵測 除了提供新增的CSS class,Modernizr還提供JavaScript方法,用來偵測瀏覽器是否支持某個功能。 ```javascript Modernizr.cssgradients; //True in Chrome, False in IE7 Modernizr.fontface; //True in Chrome, True in IE7 Modernizr.geolocation; //True in Chrome, False in IE7 if (Modernizr.canvas){ // 支持canvas } else { // 不支持canvas } if (Modernizr.touch){ // 支持觸摸屏 } else { // 不支持觸摸屏 } ``` ## 加載器 Modernizr允許根據Javascript偵測的不同結果,加載不同的腳本文件。 ```javascript Modernizr.load({ test : Modernizr.localstorage, yep : 'localStorage.js', nope : 'alt-storageSystem.js', complete : function () { enableStorgeSaveUI();} }); ``` Modernizr.load方法用來加載腳本。它的屬性如下: - test:用來測試瀏覽器是否支持某個屬性。 - yep:如果瀏覽器支持該屬性,加載的腳本。 - nope:如果瀏覽器不支持該屬性,加載的腳本。 - complete:加載完成后,運行的JavaScript代碼。 可以指定在支持某個功能的情況,所要加載的JavaScript腳本和CSS樣式。 ```javascript Modernizr.load({ test : Modernizr.touch, yep : ['js/touch.js', 'css/touchStyles.css'] }); ``` <h2 id="10.3">Datejs</h2> ## 概述 Datejs是一個用來操作日期的庫,官方網站為[datejs.com](http://www.datejs.com/)。 下載后插入網頁,就可以使用。 ```html <script type="text/javascript" src="date.js"></script> ``` 官方還提供多種語言的版本,可以選擇使用。 ```html // 美國版 <script type="text/javascript" src="date-en-US.js"></script> // 中國版 <script type="text/javascript" src="date-zh-CN.js"></script> ``` ## 方法 Datejs在原生的Date對象上面,定義了許多語義化的方法,可以方便地鏈式使用。 ### 日期信息 ```javascript Date.today() // 返回當天日期,時間定在這一天開始的00:00 Date.today().getDayName() // 今天是星期幾 Date.today().is().friday() // 今天是否為星期五,返回true或者false Date.today().is().fri() // 等同于上一行 Date.today().is().november() // 今天是否為11月,返回true或者false Date.today().is().nov() // 等同于上一行 Date.today().isWeekday() // 今天是否為工作日(周一到周五) ``` ### 日期的變更 ```javascript Date.today().next().friday() // 下一個星期五 Date.today().last().monday() // 上一個星期一 new Date().next().march() // 下個三月份的今天 new Date().last().week() // 上星期的今天 Date.today().add(5).days() // 五天后 Date.friday() // 本周的星期五 Date.march() // 今年的三月 Date.january().first().monday() // 今年一月的第一個星期一 Date.dec().final().fri() // 今年12月的最后一個星期五 // 先將日期定在本月15日的下午4點30分,然后向后推90天 Date.today().set({ day: 15, hour: 16, minute: 30 }).add({ days: 90 }) (3).days().fromNow() // 三天后 (6).months().ago() // 6個月前 (12).weeks().fromNow() // 12個星期后 (30).days().after(Date.today()) // 30天后 ``` ### 日期的解析 ```javascript Date.parse('today') Date.parse('tomorrow') Date.parse('July 8') Date.parse('July 8th, 2007') Date.parse('July 8th, 2007, 10:30 PM') Date.parse('07.15.2007') ``` <h2 id="10.4">D3.js</h2> D3.js是一個用于網頁作圖、生成互動圖形的JavaScript函數庫。它提供一個d3對象,所有方法都通過這個對象調用。 ## 操作網頁元素 D3提供了一系列操作網頁元素的方法,很類似jQuery,也是先選中某個元素(select方法),然后對其進行某種操作。 ```javascript var body = d3.select("body"); var div = body.append("div"); div.html("Hello, world!"); ``` select方法用于選中一個元素,而selectAll方法用于選中一組元素。 ```javascript var section = d3.selectAll("section"); var div = section.append("div"); div.html("Hello, world!"); ``` 大部分D3的方法都返回D3對象的實例,這意味著可以采用鏈式寫法。 ```javascript d3.select("body") .style("color", "black") .style("background-color", "white"); ``` 需要注意的是append方法返回一個新對象。 ```javascript d3.selectAll("section") .attr("class", "special") .append("div") .html("Hello, world!"); ``` ## 生成svg元素 D3作圖需要svg元素,可以用JavaScript代碼動態生成。 ```javascript var v = d3.select("#graph") .append("svg"); v.attr("width", 900).attr("height", 400); ``` ## 生成圖形 ### 選中對象集 selectAll方法不僅可以選中現有的網頁元素,還可以選中不存在的網頁元素。 ```javascript d3.select(".chart") .selectAll("div"); ``` 上面代碼表示,selectAll方法選中了.chart元素下面所有現有和將來可能出現的div元素。 ### 綁定數據 data方法用于對選中的結果集綁定數據。 ```javascript var data = [4, 8, 15, 16, 23, 42, 12]; d3.select(".chart") .selectAll("div") .data(data) .enter().append("div") .style("width", function(d) { return d * 10 + "px"; }) .text(function(d) { return d; }); ``` 上面代碼中,enter方法和append方法表示由于此時div元素還不存在,必須根據數據的個數將它們創造出來。style方法和text方法的參數是函數,表示函數的運行結果就是設置網頁元素的值。 上面代碼的運行結果是生成一個條狀圖,但是沒有對條狀圖的長度進行控制,下面采用scale.linear方法對數據長度進行設置。 ```javascript var data = [4, 8, 15, 16, 23, 42, 12]; var x = d3.scale.linear() .domain([0, d3.max(data)]) .range([0, 420]); d3.select(".chart") .selectAll("div") .data(data) .enter().append("div") .style("width", function(d) { return x(d) + "px"; }) .text(function(d) { return d; }); ``` ## 操作SVG圖形 使用SVG圖形生成條形圖,首先是選中矢量圖格式,然后每個數據值生成一個g元素(group),再在每個g元素內部生成一個rect元素和text元素。 ```javascript var width = 840, barHeight = 20; var x = d3.scale.linear() .domain([0, d3.max(dataArray)]) .range([0, width]); var chart = d3.select(".bar-chart-svg") .attr("width", width) .attr("height", barHeight * dataArray.length); var bar = chart.selectAll("g") .data(dataArray) .enter().append("g") .attr("transform", function(d, i) { return "translate(0," + i * barHeight + ")"; }); bar.append("rect") .attr("width", x) .attr("height", barHeight - 1); bar.append("text") .attr("x", function(d) { return x(d) - 3; }) .attr("y", barHeight / 2) .attr("dy", ".35em") .text(function(d) { return d; }); ``` ## 加載XML文件 ```javascript d3.xml('example', 'image/svg+xml', function (error, data) { if (error) { console.log('加載SVG文件出錯!', error); } else { // 處理SVG文件 } }); ``` <h2 id="10.5">設計模式</h2> "設計模式"(Design Pattern)是針對編程中經常出現的、具有共性的問題,所提出的解決方法。著名的《設計模式》一書一共提出了23種模式。 ## Singleton Singleton模式指的是一個“類”只能創造一個實例。由于JavaScript語言沒有類,單個對象可以直接生成,所以實際上,沒有必要部署Singleton模式。但是,還是可以做到的。 ```javascript var someClass = { _singleton: null, getSingleton: function() { if (!this._singleton) { this._singleton = { // some code here } } return this._singleton; } }; var instance = someClass.getSingleton(); ``` 生成實例的時候,調用getSingleton方法。該方法首先檢查_singleton屬性是否有值,如果有值就返回這個屬性,如果為空則生成新的實例,并賦值給_singleton屬性,然后返回這個實例。這樣就保證了生成的實例都是同一個對象。 為了保證實例不被改寫,可以關閉它的寫入開關。 ```javascript Object.defineProperty(namespace, "singleton", { writable: false, configurable: false, value: { ... } }); ``` 也可以考慮使用Object.preventExtensions()、Object.seal()、Object.freeze()等方法,限制對實例進行寫操作。 <h2 id="10.6">排序算法</h2> 排序算法是將一系列的值按照順序進行排列的方法。 ## 冒泡排序 ### 簡介 冒泡排序(Bubble Sort)是最易懂的排序算法,但是效率較低,生產環境中很少使用。 它的基本思想是: 1. 依次比較相鄰的兩個數,如果不符合排序規則,則調換兩個數的位置。這樣一遍比較下來,能夠保證最大(或最小)的數排在最后一位。 2. 再對最后一位以外的數組,重復前面的過程,直至全部排序完成。 由于每進行一次這個過程,在該次比較的最后一個位置上,正確的數會自己冒出來,就好像“冒泡”一樣,這種算法因此得名。 以對數組[3, 2, 4, 5, 1] 進行從小到大排序為例,步驟如下: 1. 第一位的“3”與第二位的“2”進行比較,3大于2,互換位置,數組變成[2, 3, 4, 5, 1] 。 2. 第二位的“3”與第三位的“4”進行比較,3小于4,數組不變。 3. 第三位的“4”與第四位的“5”進行比較,4小于5,數組不變。 4. 第四位的“5”與第五位的“1”進行比較,5大于1,互換位置,數組變成[2, 3, 4, 1, 5] 。 第一輪排序完成,可以看到最后一位的5,已經是正確的數了。然后,再對剩下的數[2, 3, 4, 1] 重復這個過程,每一輪都會在本輪最后一位上出現正確的數。直至剩下最后一個位置,所有排序結束。 ### 算法實現 先定義一個交換函數,作用是交換兩個位置的值。 ```javascript function swap(myArray, p1, p2){ var temp = myArray[p1]; myArray[p1] = myArray[p2]; myArray[p2] = temp; } ``` 然后定義主函數。 ```javascript function bubbleSort(myArray){ var len = myArray.length, i, j, stop; for (i=0; i < len; i++){ for (j=0, stop=len-1-i; j < stop; j++){ if (myArray[j] > myArray[j+1]){ swap(myArray, j, j+1); } } } return myArray; } ``` ## 選擇排序 ### 簡介 選擇排序(Selection Sort)與冒泡排序類似,也是依次對相鄰的數進行兩兩比較。不同之處在于,它不是每比較一次就調換位置,而是一輪比較完畢,找到最大值(或最小值)之后,將其放在正確的位置,其他數的位置不變。 以對數組[3, 2, 4, 5, 1] 進行從小到大排序為例,步驟如下: 1. 假定第一位的“3”是最小值。 2. 最小值“3”與第二位的“2”進行比較,2小于3,所以新的最小值是第二位的“2”。 3. 最小值“2”與第三位的“4”進行比較,2小于4,最小值不變。 4. 最小值“2”與第四位的“5”進行比較,2小于5,最小值不變。 5. 最小值“2”與第五位的“1”進行比較,1小于2,所以新的最小值是第五位的“1”。 6. 第五位的“1”與第一位的“3”互換位置,數組變為[1, 2, 4, 5, 3]。 這一輪比較結束后,最小值“1”已經排到正確的位置了,然后對剩下的[2, 4, 5, 3]重復上面的過程。每一輪排序都會將該輪的最小值排到正確的位置,直至剩下最后一個位置,所有排序結束。 ### 算法實現 先定義一個交換函數。 ```javascript function swap(myArray, p1, p2){ var temp = myArray[p1]; myArray[p1] = myArray[p2]; myArray[p2] = temp; } ``` 然后定義主函數。 ```javascript function selectionSort(myArray){ var len = myArray.length, min; for (i=0; i < len; i++){ // 將當前位置設為最小值 min = i; // 檢查數組其余部分是否更小 for (j=i+1; j < len; j++){ if (myArray[j] < myArray[min]){ min = j; } } // 如果當前位置不是最小值,將其換為最小值 if (i != min){ swap(myArray, i, min); } } return myArray; } ``` ## 插入排序 ### 簡介 插入排序(insertion sort)比前面兩種排序方法都更有效率。它將數組分成“已排序”和“未排序”兩部分,一開始的時候,“已排序”的部分只有一個元素,然后將它后面一個元素從“未排序”部分插入“已排序”部分,從而“已排序”部分增加一個元素,“未排序”部分減少一個元素。以此類推,完成全部排序。 以對數組[3, 2, 4, 5, 1] 進行從小到大排序為例,步驟如下: 1. 將數組分成[3]和[2, 4, 5, 1]兩部分,前者是已排序的,后者是未排序的。 2. 取出未排序部分的第一個元素“2”,與已排序部分最后一個元素“3”比較,因為2小于3,所以2排在3前面,整個數組變成[2, 3]和[4, 5, 1]兩部分。 3. 取出未排序部分的第一個元素“4”,與已排序部分最后一個元素“3”比較,因為4大于3,所以4排在3后面,整個數組變成[2, 3, 4]和[5, 1]兩部分。 4. 取出未排序部分的第一個元素“5”,與已排序部分最后一個元素“4”比較,因為5大于4,所以5排在4后面,整個數組變成[2, 3, 4, 5]和[1]兩部分。 5. 取出未排序部分的第一個元素“1”,與已排序部分最后一個元素“5”比較,因為1小于5,所以再與前一個元素“4”比較;因為1小于4,再與前一個元素“3”比較;因為1小于3,再與前一個元素“2”比較;因為小于1小于2,所以“1”排在2的前面,整個數組變成[1, 2, 3, 4, 5]。 ### 算法實現 算法的實現如下: ```javascript function insertionSort(myArray) { var len = myArray.length, // 數組的長度 value, // 當前比較的值 i, // 未排序部分的當前位置 j; // 已排序部分的當前位置 for (i=0; i < len; i++) { // 儲存當前位置的值 value = myArray[i]; /* * 當已排序部分的當前元素大于value, * 就將當前元素向后移一位,再將前一位與value比較 */ for (j=i-1; j > -1 && myArray[j] > value; j--) { myArray[j+1] = myArray[j]; } myArray[j+1] = value; } return myArray; } ``` ## 合并排序 ### 簡介 前面三種排序算法只有教學價值,因為效率低,很少實際使用。合并排序(Merge sort)則是一種被廣泛使用的排序方法。 它的基本思想是,將兩個已經排序的數組合并,要比從頭開始排序所有元素來得快。因此,可以將數組拆開,分成n個只有一個元素的數組,然后不斷地兩兩合并,直到全部排序完成。 以對數組[3, 2, 4, 5, 1] 進行從小到大排序為例,步驟如下: 1. 將數組分成[3, 2, 4]和[5, 1]兩部分。 2. 將[3, 2, 4]分成[3, 2]和[4]兩部分。 3. 將[3, 2]分成[3]和[2]兩部分,然后合并成[2, 3]。 4. 將[2, 3]和[4]合并成[2, 3, 4]。 5. 將[5, 1]分成[5]和[1]兩部分,然后合并成[1, 5]。 6. 將[2, 3, 4]和[1, 5]合并成[1, 2, 3, 4, 5]。 ### 算法實現 這里的關鍵是如何合并兩個已經排序的數組。具體實現請看下面的函數。 ```javascript function merge(left, right){ var result = [], il = 0, ir = 0; while (il < left.length && ir < right.length){ if (left[il] < right[ir]){ result.push(left[il++]); } else { result.push(right[ir++]); } } return result.concat(left.slice(il)).concat(right.slice(ir)); } ``` 上面的merge函數,合并兩個已經按升序排好序的數組。首先,比較兩個數組的第一個元素,將其中較小的一個放入result數組;然后,將其中較大的一個與另一個數組的第二個元素進行比較,再將其中較小的一個放入result數組的第二個位置。以此類推,直到一個數組的所有元素都進入result數組為止,再將另一個數組剩下的元素接著result數組后面返回(使用concat方法)。 有了merge函數,就可以對任意數組排序了。基本方法是將數組不斷地拆成兩半,直到每一半只包含零個元素或一個元素為止,然后就用merge函數,將拆成兩半的數組不斷合并,直到合并成一整個排序完成的數組。 ```javascript function mergeSort(myArray){ if (myArray.length < 2) { return myArray; } var middle = Math.floor(myArray.length / 2), left = myArray.slice(0, middle), right = myArray.slice(middle); return merge(mergeSort(left), mergeSort(right)); } ``` 上面的代碼有一個問題,就是返回的是一個全新的數組,會多占用空間。因此,修改上面的函數,使之在原地排序,不多占用空間。 ```javascript function mergeSort(myArray){ if (myArray.length < 2) { return myArray; } var middle = Math.floor(myArray.length / 2), left = myArray.slice(0, middle), right = myArray.slice(middle), params = merge(mergeSort(left), mergeSort(right)); // 在返回的數組頭部,添加兩個元素,第一個是0,第二個是返回的數組長度 params.unshift(0, myArray.length); // splice用來替換數組元素,它接受多個參數, // 第一個是開始替換的位置,第二個是需要替換的個數,后面就是所有新加入的元素。 // 因為splice不接受數組作為參數,所以采用apply的寫法。 // 這一句的意思就是原來的myArray數組替換成排序后的myArray myArray.splice.apply(myArray, params); // 返回排序后的數組 return myArray; } ``` ## 快速排序 ### 簡介 快速排序(quick sort)是公認最快的排序算法之一,有著廣泛的應用。 它的基本思想很簡單:先確定一個“支點”(pivot),將所有小于“支點”的值都放在該點的左側,大于“支點”的值都放在該點的右側,然后對左右兩側不斷重復這個過程,直到所有排序完成。 具體做法是: 1. 確定“支點”(pivot)。雖然數組中任意一個值都能作為“支點”,但通常是取數組的中間值。 2. 建立兩端的指針。左側的指針指向數組的第一個元素,右側的指針指向數組的最后一個元素。 3. 左側指針的當前值與“支點”進行比較,如果小于“支點”則指針向后移動一位,否則指針停在原地。 4. 右側指針的當前值與“支點”進行比較,如果大于“支點”則指針向前移動一位,否則指針停在原地。 5. 左側指針的位置與右側指針的位置進行比較,如果前者大于等于后者,則本次排序結束;否則,左側指針的值與右側指針的值相交換。 6. 對左右兩側重復第2至5步。 以對數組[3, 2, 4, 5, 1] 進行從小到大排序為例,步驟如下: 1. 選擇中間值“4”作為“支點”。 2. 第一個元素3小于4,左側指針向后移動一位;第二個元素2小于4,左側指針向后移動一位;第三個元素4等于4,左側指針停在這個位置(數組的第2位)。 3. 倒數第一個元素1小于4,右側指針停在這個位置(數組的第4位)。 4. 左側指針的位置(2)小于右側指針的位置(4),兩個位置的值互換,數組變成[3, 2, 1, 5, 4]。 5. 左側指針向后移動一位,第四個元素5大于4,左側指針停在這個位置(數組的第3位)。 6. 右側指針向前移動一位,第四個元素5大于4,右側指針移動向前移動一位,第三個元素1小于4,右側指針停在這個位置(數組的第3位)。 7. 左側指針的位置(3)大于右側指針的位置(2),本次排序結束。 8. 對 [3, 2, 1]和[5, 4]兩部分各自不斷重復上述步驟,直到排序完成。 ### 算法實現 首先部署一個swap函數,用于互換兩個位置的值。 ```javascript function swap(myArray, firstIndex, secondIndex){ var temp = myArray[firstIndex]; myArray[firstIndex] = myArray[secondIndex]; myArray[secondIndex] = temp; } ``` 然后,部署一個partition函數,用于完成一輪排序。 ```javascript function partition(myArray, left, right) { var pivot = myArray[Math.floor((right + left) / 2)], i = left, j = right; while (i <= j) { while (myArray[i] < pivot) { i++; } while (myArray[j] > pivot) { j--; } if (i <= j) { swap(myArray, i, j); i++; j--; } } return i; } ``` 接下來,就是遞歸上面的過程,完成整個排序。 ```javascript function quickSort(myArray, left, right) { if (myArray.length < 2) return myArray; left = (typeof left !== "number" ? 0 : left); right = (typeof right !== "number" ? myArray.length - 1 : right); var index = partition(myArray, left, right); if (left < index - 1) { quickSort(myArray, left, index - 1); } if (index < right) { quickSort(myArray, index, right); } return myArray; } ``` <h2 id="10.7">PhantomJS</h2> ## 概述 有時,我們需要瀏覽器處理網頁,但并不需要瀏覽,比如生成網頁的截圖、抓取網頁數據等操作。[PhantomJS](http://phantomjs.org/)的功能,就是提供一個瀏覽器環境的命令行接口,你可以把它看作一個“虛擬瀏覽器”,除了不能瀏覽,其他與正常瀏覽器一樣。它的內核是WebKit引擎,不提供圖形界面,只能在命令行下使用,我們可以用它完成一些特殊的用途。 PhantomJS是二進制程序,需要[安裝](http://phantomjs.org/download.html)后使用。 ```bash $ npm install phantomjs -g ``` 使用下面的命令,查看是否安裝成功。 ```bash $ phantomjs --version ``` ## REPL環境 phantomjs提供了一個完整的REPL環境,允許用戶通過命令行與PhantomJS互動。鍵入phantomjs,就進入了該環境。 ```bash $ phantomjs ``` 這時會跳出一個phantom提示符,就可以輸入Javascript命令了。 ```bash phantomjs> 1+2 3 phantomjs> function add(a,b) { return a+b; } undefined phantomjs> add(1,2) 3 ``` 按ctrl+c可以退出該環境。 下面,我們把上面的add()函數寫成一個文件add.js文件。 ```javascript // add.js function add(a,b){ return a+b; } console.log(add(1,2)); phantom.exit(); ``` 上面的代碼中,console.log()的作用是在終端窗口顯示,phantom.exit()則表示退出phantomjs環境。一般來說,不管什么樣的程序,exit這一行都不能少。 現在,運行該程序。 ```bash $ phantomjs add.js ``` 終端窗口就會顯示結果為3。 下面是更多的例子。 ```javascript phantomjs> phantom.version { "major": 1, "minor": 5, "patch": 0 } phantomjs> console.log("phantom is awesome") phantom is awesome phantomjs> window.navigator { "cookieEnabled": true, "language": "en-GB", "productSub": "20030107", "product": "Gecko", // ... } ``` ## webpage模塊 webpage模塊是PhantomJS的核心模塊,用于網頁操作。 ```javascript var webPage = require('webpage'); var page = webPage.create(); ``` 上面代碼表示加載PhantomJS的webpage模塊,并創建一個實例。 下面是webpage實例的屬性和方法介紹。 ### open() open方法用于打開具體的網頁。 ```javascript var page = require('webpage').create(); page.open('http://slashdot.org', function (s) { console.log(s); phantom.exit(); }); ``` 上面代碼中,open()方法,用于打開具體的網頁。它接受兩個參數。第一個參數是網頁的網址,這里打開的是著名新聞網站[Slashdot](http://slashdot.org),第二個參數是回調函數,網頁打開后該函數將會運行,它的參數是一個表示狀態的字符串,如果打開成功就是success,否則就是fail。 注意,只要接收到服務器返回的結果,PhantomJS就會報告網頁打開成功,而不管服務器是否返回404或500錯誤。 open方法默認使用GET方法,與服務器通信,但是也可以使用其他方法。 ```javascript var webPage = require('webpage'); var page = webPage.create(); var postBody = 'user=username&password=password'; page.open('http://www.google.com/', 'POST', postBody, function(status) { console.log('Status: ' + status); // Do other things here... }); ``` 上面代碼中,使用POST方法向服務器發送數據。open方法的第二個參數用來指定HTTP方法,第三個參數用來指定該方法所要使用的數據。 open方法還允許提供配置對象,對HTTP請求進行更詳細的配置。 ```javascript var webPage = require('webpage'); var page = webPage.create(); var settings = { operation: "POST", encoding: "utf8", headers: { "Content-Type": "application/json" }, data: JSON.stringify({ some: "data", another: ["custom", "data"] }) }; page.open('http://your.custom.api', settings, function(status) { console.log('Status: ' + status); // Do other things here... }); ``` ### evaluate() evaluate方法用于打開網頁以后,在頁面中執行JavaScript代碼。 ```javascript var page = require('webpage').create(); page.open(url, function(status) { var title = page.evaluate(function() { return document.title; }); console.log('Page title is ' + title); phantom.exit(); }); ``` 網頁內部的console語句,以及evaluate方法內部的console語句,默認不會顯示在命令行。這時可以采用onConsoleMessage回調函數,上面的例子可以改寫如下。 ```javascript var page = require('webpage').create(); page.onConsoleMessage = function(msg) { console.log('Page title is ' + msg); }; page.open(url, function(status) { page.evaluate(function() { console.log(document.title); }); phantom.exit(); }); ``` 上面代碼中,evaluate方法內部有console語句,默認不會輸出在命令行。這時,可以用onConsoleMessage方法監聽這個事件,進行處理。 ### includeJs() includeJs方法用于頁面加載外部腳本,加載結束后就調用指定的回調函數。 ```javascript var page = require('webpage').create(); page.open('http://www.sample.com', function() { page.includeJs("http://path/to/jquery.min.js", function() { page.evaluate(function() { $("button").click(); }); phantom.exit() }); }); ``` 上面的例子在頁面中注入jQuery腳本,然后點擊所有的按鈕。需要注意的是,由于是異步加載,所以`phantom.exit()`語句要放在`page.includeJs()`方法的回調函數之中,否則頁面會過早退出。 ### render() render方法用于將網頁保存成圖片,參數就是指定的文件名。該方法根據后綴名,將網頁保存成不同的格式,目前支持PNG、GIF、JPEG和PDF。 ```javascript var webPage = require('webpage'); var page = webPage.create(); page.viewportSize = { width: 1920, height: 1080 }; page.open("http://www.google.com", function start(status) { page.render('google_home.jpeg', {format: 'jpeg', quality: '100'}); phantom.exit(); }); ``` 該方法還可以接受一個配置對象,format字段用于指定圖片格式,quality字段用于指定圖片質量,最小為0,最大為100。 ### viewportSize,zoomFactor viewportSize屬性指定瀏覽器視口的大小,即網頁加載的初始瀏覽器窗口大小。 ```javascript var webPage = require('webpage'); var page = webPage.create(); page.viewportSize = { width: 480, height: 800 }; ``` viewportSize的Height字段必須指定,不可省略。 zoomFactor屬性用來指定渲染時(render方法和renderBase64方法)頁面的放大系數,默認是1(即100%)。 ```javascript var webPage = require('webpage'); var page = webPage.create(); page.zoomFactor = 0.25; page.render('capture.png'); ``` ### onResourceRequested onResourceRequested屬性用來指定一個回調函數,當頁面請求一個資源時,會觸發這個回調函數。它的第一個參數是HTTP請求的元數據對象,第二個參數是發出的網絡請求對象。 HTTP請求包括以下字段。 - id:所請求資源的編號 - method:使用的HTTP方法 - url:所請求的資源 URL - time:一個包含請求時間的Date對象 - headers:HTTP頭信息數組 網絡請求對象包含以下方法。 - abort():終止當前的網絡請求,這會導致調用onResourceError回調函數。 - changeUrl(newUrl):改變當前網絡請求的URL。 - setHeader(key, value):設置HTTP頭信息。 ```javascript var webPage = require('webpage'); var page = webPage.create(); page.onResourceRequested = function(requestData, networkRequest) { console.log('Request (#' + requestData.id + '): ' + JSON.stringify(requestData)); }; ``` ### onResourceReceived onResourceReceived屬性用于指定一個回調函數,當網頁收到所請求的資源時,就會執行該回調函數。它的參數就是服務器發來的HTTP回應的元數據對象,包括以下字段。 - id:所請求的資源編號 - url:所請求的資源的URL - time:包含HTTP回應時間的Date對象 - headers:HTTP頭信息數組 - bodySize:解壓縮后的收到的內容大小 - contentType:接到的內容種類 - redirectURL:重定向URL(如果有的話) - stage:對于多數據塊的HTTP回應,頭一個數據塊為start,最后一個數據塊為end。 - status:HTTP狀態碼,成功時為200。 - statusText:HTTP狀態信息,比如OK。 如果HTTP回應非常大,分成多個數據塊發送,onResourceReceived會在收到每個數據塊時觸發回調函數。 ```javascript var webPage = require('webpage'); var page = webPage.create(); page.onResourceReceived = function(response) { console.log('Response (#' + response.id + ', stage "' + response.stage + '"): ' + JSON.stringify(response)); }; ``` ## system模塊 system模塊可以加載操作系統變量,system.args就是參數數組。 ```javascript var page = require('webpage').create(), system = require('system'), t, address; // 如果命令行沒有給出網址 if (system.args.length === 1) { console.log('Usage: page.js <some URL>'); phantom.exit(); } t = Date.now(); address = system.args[1]; page.open(address, function (status) { if (status !== 'success') { console.log('FAIL to load the address'); } else { t = Date.now() - t; console.log('Loading time ' + t + ' ms'); } phantom.exit(); }); ``` 使用方法如下: ```bash $ phantomjs page.js http://www.google.com ``` ## 應用 Phantomjs可以實現多種應用。 ### 過濾資源 處理頁面的時候,有時不希望加載某些特定資源。這時,可以對URL進行匹配,一旦符合規則,就中斷對資源的連接。 ```javascript page.onResourceRequested = function(requestData, request) { if ((/http:\/\/.+?\.css$/gi).test(requestData['url'])) { console.log('Skipping', requestData['url']); request.abort(); } }; ``` 上面代碼一旦發現加載的資源是CSS文件,就會使用`request.abort`方法中斷連接。 ### 截圖 最簡單的生成網頁截圖的方法如下。 ```javascript var page = require('webpage').create(); page.open('http://google.com', function () { page.render('google.png'); phantom.exit(); }); ``` page對象代表一個網頁實例;open方法表示打開某個網址,它的第一個參數是目標網址,第二個參數是網頁載入成功后,運行的回調函數;render方法則是渲染頁面,然后以圖片格式輸出,該方法的參數就是輸出的圖片文件名。 除了簡單截圖以外,還可以設置各種截圖參數。 ```javascript var page = require('webpage').create(); page.open('http://google.com', function () { page.zoomFactor = 0.25; console.log(page.renderBase64()); phantom.exit(); }); ``` zoomFactor表示將截圖縮小至原圖的25%大小;renderBase64方法則是表示將截圖(PNG格式)編碼成Base64格式的字符串輸出。 下面的例子則是使用了更多參數。 ```javascript // page.js var page = require('webpage').create(); page.settings.userAgent = 'WebKit/534.46 Mobile/9A405 Safari/7534.48.3'; page.settings.viewportSize = { width: 400, height: 600 }; page.open('http://slashdot.org', function (status) { if (status !== 'success') { console.log('Unable to load!'); phantom.exit(); } else { var title = page.evaluate(function () { var posts = document.getElementsByClassName("article"); posts[0].style.backgroundColor = "#FFF"; return document.title; }); window.setTimeout(function () { page.clipRect = { top: 0, left: 0, width: 600, height: 700 }; page.render(title + "1.png"); page.clipRect = { left: 0, top: 600, width: 400, height: 600 }; page.render(title + '2.png'); phantom.exit(); }, 1000); } }); ``` 上面代碼中的幾個屬性和方法解釋如下: - settings.userAgent:指定HTTP請求的userAgent頭信息,上面例子是手機瀏覽器的userAgent。 - settings.viewportSize:指定瀏覽器窗口的大小,這里是400x600。 - evaluate():用來在網頁上運行Javascript代碼。在這里,我們抓取第一條新聞,然后修改背景顏色,并返回該條新聞的標題。 - clipRect:用來指定網頁截圖的大小,這里的截圖左上角從網頁的(0. 0)坐標開始,寬600像素,高700像素。如果不指定這個值,就表示對整張網頁截圖。 - render():根據clipRect的范圍,在當前目錄下生成以第一條新聞的名字命名的截圖。 ### 抓取圖片 使用官方網站提供的[rasterize.js](https://github.com/ariya/phantomjs/blob/master/examples/rasterize.js),可以抓取網絡上的圖片,將起保存在本地。 ```javascript phantomjs rasterize.js http://ariya.github.com/svg/tiger.svg tiger.png ``` 使用[rasterize.js](https://github.com/ariya/phantomjs/blob/master/examples/rasterize.js),還可以將網頁保存為pdf文件。 ```javascript phantomjs rasterize.js 'http://en.wikipedia.org/w/index.php?title=Jakarta&printable=yes' jakarta.pdf ``` ### 生成網頁 phantomjs可以生成網頁,使用content方法指定網頁的HTML代碼。 ```javascript var page = require('webpage').create(); page.viewportSize = { width: 400, height : 400 }; page.content = '<html><body><canvas id="surface"></canvas></body></html>'; phantom.exit(); ``` 官方網站有一個[例子](https://github.com/ariya/phantomjs/blob/master/examples/colorwheel.js),通過創造svg圖片,然后截圖保存成png文件。 ![](https://lh3.googleusercontent.com/-xSIzxPtJULw/TVzeP4NPMDI/AAAAAAAAB10/k-c8jB6I5Cg/s288/colorwheel.png) <h2 id="10.8">Bower:客戶端庫管理工具</h2> ## 概述 隨著網頁功能變得越來越復雜,同一張網頁加載多個JavaScript函數庫早已是家常便飯。開發者越來越需要一個工具,對瀏覽器端的各種庫進行管理,比如搜索、自動安裝\卸載、檢查更新、確保依賴關系等等。Bower就是為了解決這個問題而誕生的針對瀏覽器端的庫管理工具。 Bower基于node.js,所以安裝之前,必須先確保已安裝node.js。 ```bash $ sudo npm install bower --global ``` 運行上面的命令以后,Bower就已經安裝在你的系統中了。運行幫助命令,查看Bower是否安裝成功。 ```bash $ bower help ``` 下面的命令可以更新或卸載Bower。 ```bash # 更新 $ sudo npm update -g bower # 卸載 $ sudo npm uninstall --global bower ``` ## 常用操作 ### 項目初始化 在項目根目錄下,運行下面的命令,進行初始化。 ```bash $ bower init ``` 通過回答幾個問題,就會自動生成bower.json文件。這是項目的配置文件,下面是一個例子。 ```javascript { "name": "app-name", "version": "0.1.0", "main": ["path/to/app.html", "path/to/app.css", "path/to/app.js"], "ignore": [".jshintrc","**/*.txt"], "dependencies": { "sass-bootstrap": "~3.0.0", "modernizr": "~2.6.2", "jquery": "latests" }, "devDependencies": {"qunit": ">1.11.0"} } ``` 有了bower.json文件以后,就可以用bower install命令,一下子安裝所有庫。 ```bash $ bower install ``` bower.json文件存放在庫的根目錄下,它的作用是(1)保存項目的庫信息,供項目安裝時使用,(2)向Bower.com提交你的庫,該網站會讀取bower.json,列入在線索引。 ```bash $ bower register <my-package-name> <git-endpoint> # 實例:在 bower.com 登記jquery $ bower register jquery git://github.com/jquery/jquery ``` 注意,如果你的庫與現有的庫重名,就會提交失敗。 ### 庫的安裝 bower install命令用于安裝某個庫,需要指明庫的名字。 ```bash $ bower install backbone ``` Bower會使用庫的名字,去在線索引中搜索該庫的網址。某些情況下,如果一個庫很新(或者你不想使用默認網址),可能需要我們手動指定該庫的網址。 ```bash $ bower install git://github.com/documentcloud/backbone.git $ bower install http://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.0.0/backbone-min.js $ bower install ./some/path/relative/to/this/directory/backbone.js ``` 上面的命令說明,指定的網址可以是github地址、http網址、本地文件。 默認情況下,會安裝該庫的最新版本,但是也可以手動指定版本號。 ```bash $ bower install jquery-ui#1.10.1 ``` 上面的命令指定安裝jquery-ui的1.10.1版。 如果某個庫依賴另一個庫,安裝時默認將所依賴的庫一起安裝。比如,jquery-ui依賴jquery,安裝時會連jquery一起安裝。 安裝后的庫默認存放在項目的bower_components子目錄,如果要指定其他位置,可在.bowerrc文件的directory屬性設置。 ### 庫的搜索和查看 bower search命令用于使用關鍵字,從在線索引中搜索相關庫。 ```bash bower search jquery ``` 上面命令會得到下面這樣的結果。 ```bash Search results: jquery git://github.com/components/jquery.git jquery-ui git://github.com/components/jqueryui jquery.cookie git://github.com/carhartl/jquery-cookie.git jquery-placeholder git://github.com/mathiasbynens/jquery-placeholder.git jquery-file-upload git://github.com/blueimp/jQuery-File-Upload.git jasmine-jquery git://github.com/velesin/jasmine-jquery jquery.ui git://github.com/jquery/jquery-ui.git ... ``` bower info命令用于查看某個庫的詳細信息。 ```bash bower info jquery-ui ``` 查看結果會列出該庫的依賴關系(dependencies),以及可以得到的版本(Available versions)。 ### 庫的更新和卸載 bower update用于更新一個庫,將其更新為最新版本。 ```bash $ bower update jquery-ui ``` 如果不給出庫名,則更新所有庫。 bower uninstall命令用于卸載指定的庫。 ```bash $ bower uninstall jquery-ui ``` 注意,默認情況下會連所依賴的庫一起卸載。比如,jquery-ui依賴jquery,卸載時會連jquery一起卸載,除非還有別的庫依賴jquery。 ### 列出所有庫 bower list或bower ls命令,用于列出項目所使用的所有庫。 ```bash Bower list Bower ls ``` ## 配置文件.bowerrc 項目根目錄下(也可以放在用戶的主目錄下)的.bowerrc文件是Bower的配置文件,它大概像下面這樣。 ```javascript { "directory" : "components", "json" : "bower.json", "endpoint" : "https://Bower.herokuapp.com", "searchpath" : "", "shorthand_resolver" : "" } ``` 其中的屬性含義如下。 - directory:存放庫文件的子目錄名。 - json:描述各個庫的json文件名。 - endpoint:在線索引的網址,用來搜索各種庫。 - searchpath:一個數組,儲存備選的在線索引網址。如果某個庫在endpoint中找不到,則繼續搜索該屬性指定的網址,通常用于放置某些不公開的庫。 - shorthand_resolver:定義各個庫名稱簡寫形式。 <h2 id="10.9">Grunt:任務自動管理工具</h2> 在Javascript的開發過程中,經常會遇到一些重復性的任務,比如合并文件、壓縮代碼、檢查語法錯誤、將Sass代碼轉成CSS代碼等等。通常,我們需要使用不同的工具,來完成不同的任務,既重復勞動又非常耗時。Grunt就是為了解決這個問題而發明的工具,可以幫助我們自動管理和運行各種任務。 簡單說,Grunt是一個自動任務運行器,會按照預先設定的順序自動運行一系列的任務。這可以簡化工作流程,減輕重復性工作帶來的負擔。 ## 安裝 Grunt基于Node.js,安裝之前要先安裝Node.js,然后運行下面的命令。 ```bash sudo npm install grunt-cli -g ``` grunt-cli表示安裝的是grunt的命令行界面,參數g表示全局安裝。 Grunt使用模塊結構,除了安裝命令行界面以外,還要根據需要安裝相應的模塊。這些模塊應該采用局部安裝,因為不同項目可能需要同一個模塊的不同版本。 首先,在項目的根目錄下,創建一個文本文件package.json,指定當前項目所需的模塊。下面就是一個例子。 ```javascript { "name": "my-project-name", "version": "0.1.0", "author": "Your Name", "devDependencies": { "grunt": "0.x.x", "grunt-contrib-jshint": "*", "grunt-contrib-concat": "~0.1.1", "grunt-contrib-uglify": "~0.1.0", "grunt-contrib-watch": "~0.1.4" } } ``` 上面這個package.json文件中,除了注明項目的名稱和版本以外,還在devDependencies屬性中指定了項目依賴的grunt模塊和版本:grunt核心模塊為最新的0.x.x版,jshint插件為最新版本,concat插件不低于0.1.1版,uglify插件不低于0.1.0版,watch插件不低于0.1.4版。 然后,在項目的根目錄下運行下面的命令,這些插件就會被自動安裝在node_modules子目錄。 ```bash npm install ``` 上面這種方法是針對已有package.json的情況。如果想要自動生成package.json文件,可以使用npm init命令,按照屏幕提示回答所需模塊的名稱和版本即可。 ```bash npm init ``` 如果已有的package.json文件不包括Grunt模塊,可以在直接安裝Grunt模塊的時候,加上--save-dev參數,該模塊就會自動被加入package.json文件。 ```bash npm install <module> --save-dev ``` 比如,對應上面package.json文件指定的模塊,需要運行以下npm命令。 ```bash npm install grunt --save-dev npm install grunt-contrib-jshint --save-dev npm install grunt-contrib-concat --save-dev npm install grunt-contrib-uglify --save-dev npm install grunt-contrib-watch --save-dev ``` ## 命令腳本文件Gruntfile.js 模塊安裝完以后,下一步在項目的根目錄下,新建腳本文件Gruntfile.js。它是grunt的配置文件,就好像package.json是npm的配置文件一樣。Gruntfile.js就是一般的Node.js模塊的寫法。 ```javascript module.exports = function(grunt) { // 配置Grunt各種模塊的參數 grunt.initConfig({ jshint: { /* jshint的參數 */ }, concat: { /* concat的參數 */ }, uglify: { /* uglify的參數 */ }, watch: { /* watch的參數 */ } }); // 從node_modules目錄加載模塊文件 grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-watch'); // 每行registerTask定義一個任務 grunt.registerTask('default', ['jshint', 'concat', 'uglify']); grunt.registerTask('check', ['jshint']); }; ``` 上面的代碼用到了grunt代碼的三個方法: - **grunt.initConfig**:定義各種模塊的參數,每一個成員項對應一個同名模塊。 - **grunt.loadNpmTasks**:加載完成任務所需的模塊。 - **grunt.registerTask**:定義具體的任務。第一個參數為任務名,第二個參數是一個數組,表示該任務需要依次使用的模塊。default任務名表示,如果直接輸入grunt命令,后面不跟任何參數,這時所調用的模塊(該例為jshint,concat和uglify);該例的check任務則表示使用jshint插件對代碼進行語法檢查。 上面的代碼一共加載了四個模塊:jshint(檢查語法錯誤)、concat(合并文件)、uglify(壓縮代碼)和watch(自動執行)。接下來,有兩種使用方法。 (1)命令行執行某個模塊,比如 ```bash grunt jshint ``` 上面代碼表示運行jshint模塊。 (2)命令行執行某個任務。比如 ```bash grunt check ``` 上面代碼表示運行check任務。如果運行成功,就會顯示“Done, without errors.”。 如果沒有給出任務名,只鍵入grunt,就表示執行默認的default任務。 ## Gruntfile.js實例:grunt-contrib-cssmin模塊 下面通過cssmin模塊,演示如何編寫Gruntfile.js文件。cssmin模塊的作用是最小化CSS文件。 首先,在項目的根目錄下安裝該模塊。 ```bash npm install grunt-contrib-cssmin --save-dev ``` 然后,新建文件Gruntfile.js。 ```javascript module.exports = function(grunt) { grunt.initConfig({ cssmin: { minify: { expand: true, cwd: 'css/', src: ['*.css', '!*.min.css'], dest: 'css/', ext: '.min.css' }, combine: { files: { 'css/out.min.css': ['css/part1.min.css', 'css/part2.min.css'] } } } }); grunt.loadNpmTasks('grunt-contrib-cssmin'); grunt.registerTask('default', ['cssmin:minify','cssmin:combine']); }; ``` 下面詳細解釋上面代碼中的三個方法,下面一個個來看。 **(1)grunt.loadNpmTasks** grunt.loadNpmTasks方法載入模塊文件。 ```javascript grunt.loadNpmTasks('grunt-contrib-cssmin'); ``` 你需要使用幾個模塊,這里就要寫幾條grunt.loadNpmTasks語句,將各個模塊一一加載。 如果加載模塊很多,這部分會非常冗長。而且,還存在一個問題,就是凡是在這里加載的模塊,也同時出現在package.json文件中。如果使用npm命令卸載模塊以后,模塊會自動從package.json文件中消失,但是必須手動從Gruntfile.js文件中清除,這樣很不方便,一旦忘記,還會出現運行錯誤。這里有一個解決辦法,就是安裝load-grunt-tasks模塊,然后在Gruntfile.js文件中,用下面的語句替代所有的grunt.loadNpmTasks語句。 ```javascript require('load-grunt-tasks')(grunt); ``` 這條語句的作用是自動分析package.json文件,自動加載所找到的grunt模塊。 **(2)grunt.initConfig** grunt.initConfig方法用于模塊配置,它接受一個對象作為參數。該對象的成員與使用的同名模塊一一對應。由于我們要配置的是cssmin模塊,所以里面有一個cssmin成員(屬性)。 cssmin(屬性)指向一個對象,該對象又包含多個成員。除了一些系統設定的成員(比如options),其他自定義的成員稱為目標(target)。一個模塊可以有多個目標(target),上面代碼里面,cssmin模塊共有兩個目標,一個是“minify”,用于壓縮css文件;另一個是“combine”,用于將多個css文件合并一個文件。 每個目標的具體設置,需要參考該模板的文檔。就cssmin來講,minify目標的參數具體含義如下: - **expand**:如果設為true,就表示下面文件名的占位符(即\*號)都要擴展成具體的文件名。 - **cwd**:需要處理的文件(input)所在的目錄。 - **src**:表示需要處理的文件。如果采用數組形式,數組的每一項就是一個文件名,可以使用通配符。 - **dest**:表示處理后的文件名或所在目錄。 - **ext**:表示處理后的文件后綴名。 除了上面這些參數,還有一些參數也是grunt所有模塊通用的。 - **filter**:一個返回布爾值的函數,用于過濾文件名。只有返回值為true的文件,才會被grunt處理。 - **dot**:是否匹配以點號(.)開頭的系統文件。 - **makeBase**:如果設置為true,就只匹配文件路徑的最后一部分。比如,a?b可以匹配/xyz/123/acb,而不匹配/xyz/acb/123。 關于通配符,含義如下: - \*:匹配任意數量的字符,不包括/。 - ?:匹配單個字符,不包括/。 - \*\*:匹配任意數量的字符,包括/。 - {}:允許使用逗號分隔的列表,表示“or”(或)關系。 - !:用于模式的開頭,表示只返回不匹配的情況。 比如,foo/\*.js匹配foo目錄下面的文件名以.js結尾的文件,foo/\*\*/\*.js匹配foo目錄和它的所有子目錄下面的文件名以.js結尾的文件,!\*.css表示匹配所有后綴名不為“.css”的文件。 使用通配符設置src屬性的更多例子: ```javascript {src: 'foo/th*.js'}grunt-contrib-uglify {src: 'foo/{a,b}*.js'} {src: ['foo/a*.js', 'foo/b*.js']} ``` 至于combine目標,就只有一個files參數,表示輸出文件是css子目錄下的out.min.css,輸入文件則是css子目錄下的part1.min.css和part2.min.css。 files參數的格式可以是一個對象,也可以是一個數組。 ```javascript files: { 'dest/b.js': ['src/bb.js', 'src/bbb.js'], 'dest/b1.js': ['src/bb1.js', 'src/bbb1.js'], }, // or files: [ {src: ['src/aa.js', 'src/aaa.js'], dest: 'dest/a.js'}, {src: ['src/aa1.js', 'src/aaa1.js'], dest: 'dest/a1.js'}, ], ``` 如果minify目標和combine目標的屬性設置有重合的部分,可以另行定義一個與minify和combine平行的options屬性。 ```javascript grunt.initConfig({ cssmin: { options: { /* ... */ }, minify: { /* ... */ }, combine: { /* ... */ } } }); ``` **(3)grunt.registerTask** grunt.registerTask方法定義如何調用具體的任務。“default”任務表示如果不提供參數,直接輸入grunt命令,則先運行“cssmin:minify”,后運行“cssmin:combine”,即先壓縮再合并。如果只執行壓縮,或者只執行合并,則需要在grunt命令后面指明“模塊名:目標名”。 ```bash grunt # 默認情況下,先壓縮后合并 grunt cssmin:minify # 只壓縮不合并 grunt css:combine # 只合并不壓縮 ``` 如果不指明目標,只是指明模塊,就表示將所有目標依次運行一遍。 ```bash grunt cssmin ``` ## 常用模塊設置 grunt的[模塊](http://gruntjs.com/plugins)已經超過了2000個,且還在快速增加。下面是一些常用的模塊(按字母排序)。 - **grunt-contrib-clean**:刪除文件。 - **grunt-contrib-compass**:使用compass編譯sass文件。 - **grunt-contrib-concat**:合并文件。 - **grunt-contrib-copy**:復制文件。 - **grunt-contrib-cssmin**:壓縮以及合并CSS文件。 - **grunt-contrib-imagemin**:圖像壓縮模塊。 - **grunt-contrib-jshint**:檢查JavaScript語法。 - **grunt-contrib-uglify**:壓縮以及合并JavaScript文件。 - **grunt-contrib-watch**:監視文件變動,做出相應動作。 模塊的前綴如果是grunt-contrib,就表示該模塊由grunt開發團隊維護;如果前綴是grunt(比如grunt-pakmanager),就表示由第三方開發者維護。 以下選幾個模塊,看看它們配置參數的寫法,也就是說如何在grunt.initConfig方法中配置各個模塊。 ### grunt-contrib-jshint jshint用來檢查語法錯誤,比如分號的使用是否正確、有沒有忘記寫括號等等。它在grunt.initConfig方法里面的配置代碼如下。 ```javascript jshint: { options: { eqeqeq: true, trailing: true }, files: ['Gruntfile.js', 'lib/**/*.js'] }, ``` 上面代碼先指定jshint的[檢查項目](http://www.jshint.com/docs/options/),eqeqeq表示要用嚴格相等運算符取代相等運算符,trailing表示行尾不得有多余的空格。然后,指定files屬性,表示檢查目標是Gruntfile.js文件,以及lib目錄的所有子目錄下面的JavaScript文件。 ### grunt-contrib-concat concat用來合并同類文件,它不僅可以合并JavaScript文件,還可以合并CSS文件。 ```javascript concat: { js: { src: ['lib/module1.js', 'lib/module2.js', 'lib/plugin.js'], dest: 'dist/script.js' } css: { src: ['style/normalize.css', 'style/base.css', 'style/theme.css'], dest: 'dist/screen.css' } }, ``` js目標用于合并JavaScript文件,css目標用語合并CSS文件。兩者的src屬性指定需要合并的文件(input),dest屬性指定輸出的目標文件(output)。 ### grunt-contrib-uglify uglify模塊用來壓縮代碼,減小文件體積。 ```javascript uglify: { options: { banner: bannerContent, sourceMapRoot: '../', sourceMap: 'distrib/'+name+'.min.js.map', sourceMapUrl: name+'.min.js.map' }, target : { expand: true, cwd: 'js/origin', src : '*.js', dest : 'js/' } }, ``` 上面代碼中的options屬性指定壓縮后文件的文件頭,以及sourceMap設置;target目標指定輸入和輸出文件。 ### grunt-contrib-copy [copy模塊](https://github.com/gruntjs/grunt-contrib-copy)用于復制文件與目錄。 ```javascript copy: { main: { src: 'src/*', dest: 'dest/', }, }, ``` 上面代碼將src子目錄(只包含它下面的第一層文件和子目錄),拷貝到dest子目錄下面(即dest/src目錄)。如果要更準確控制拷貝行為,比如只拷貝文件、不拷貝目錄、不保持目錄結構,可以寫成下面這樣: ```javascript copy: { main: { expand: true, cwd: 'src/', src: '**', dest: 'dest/', flatten: true, filter: 'isFile', }, }, ``` ### grunt-contrib-watch [watch模塊](https://github.com/gruntjs/grunt-contrib-watch)用來在后臺運行,監聽指定事件,然后自動運行指定的任務。 ```javascript watch: { scripts: { files: '**/*.js', tasks: 'jshint', options: { livereload: true, }, }, css: { files: '**/*.sass', tasks: ['sass'], options: { livereload: true, }, }, }, ``` 設置好上面的代碼,打開另一個進程,運行grunt watch。此后,任何的js代碼變動,文件保存后就會自動運行jshint任務;任何sass文件變動,文件保存后就會自動運行sass任務。 需要注意的是,這兩個任務的options參數之中,都設置了livereload,表示任務運行結束后,自動在瀏覽器中重載(reload)。這需要在瀏覽器中安裝[livereload插件](http://livereload.com/)。安裝后,livereload的默認端口為localhost:35729,但是也可以用livereload: 1337的形式重設端口(localhost:1337)。 ### 其他模塊 下面是另外一些有用的模塊。 **(1)grunt-contrib-clean** 該模塊用于刪除文件或目錄。 ```javascript clean: { build: { src: ["path/to/dir/one", "path/to/dir/two"] } } ``` **(2)grunt-autoprefixer** 該模塊用于為CSS語句加上瀏覽器前綴。 ```javascript autoprefixer: { build: { expand: true, cwd: 'build', src: [ '**/*.css' ], dest: 'build' } }, ``` **(3)grunt-contrib-connect** 該模塊用于在本機運行一個Web Server。 ```javascript connect: { server: { options: { port: 4000, base: 'build', hostname: '*' } } } ``` connect模塊會隨著grunt運行結束而結束,為了使它一直處于運行狀態,可以把它放在watch模塊之前運行。因為watch模塊需要手動中止,所以connect模塊也就會一直運行。 **(4)grunt-htmlhint** 該模塊用于檢查HTML語法。 ```javascript htmlhint: { build: { options: { 'tag-pair': true, 'tagname-lowercase': true, 'attr-lowercase': true, 'attr-value-double-quotes': true, 'spec-char-escape': true, 'id-unique': true, 'head-script-disabled': true, }, src: ['index.html'] } } ``` 上面代碼用于檢查index.html文件:HTML標記是否配對、標記名和屬性名是否小寫、屬性值是否包括在雙引號之中、特殊字符是否轉義、HTML元素的id屬性是否為唯一值、head部分是否沒有script標記。 **(5)grunt-contrib-sass模塊** 該模塊用于將SASS文件轉為CSS文件。 ```javascript sass: { build: { options: { style: 'compressed' }, files: { 'build/css/master.css': 'assets/sass/master.scss' } } } ``` 上面代碼指定輸出文件為build/css/master.css,輸入文件為assets/sass/master.scss。 **(6)grunt-markdown** 該模塊用于將markdown文檔轉為HTML文檔。 ```javascript markdown: { all: { files: [ { expand: true, src: '*.md', dest: 'docs/html/', ext: '.html' } ], options: { template: 'templates/index.html', } } }, ``` 上面代碼指定將md后綴名的文件,轉為docs/html/目錄下的html文件。template屬性指定轉換時采用的模板,模板樣式如下。 ```html <!DOCTYPE html> <html> <head> <title>Document</title> </head> <body> <div id="main" class="container"> <%=content%> </div> </body> </html> ``` <h2 id="10.10">RequireJS和AMD規范</h2> ## 概述 RequireJS是一個工具庫,主要用于客戶端的模塊管理。它可以讓客戶端的代碼分成一個個模塊,實現異步或動態加載,從而提高代碼的性能和可維護性。它的模塊管理遵守[AMD規范](https://github.com/amdjs/amdjs-api/wiki/AMD)(Asynchronous Module Definition)。 RequireJS的基本思想是,通過define方法,將代碼定義為模塊;通過require方法,實現代碼的模塊加載。 首先,將require.js嵌入網頁,然后就能在網頁中進行模塊化編程了。 ```javascript <script data-main="scripts/main" src="scripts/require.js"></script> ``` 上面代碼的data-main屬性不可省略,用于指定主代碼所在的腳本文件,在上例中為scripts子目錄下的main.js文件。用戶自定義的代碼就放在這個main.js文件中。 ### define方法:定義模塊 define方法用于定義模塊,RequireJS要求每個模塊放在一個單獨的文件里。 按照是否依賴其他模塊,可以分成兩種情況討論。第一種情況是定義獨立模塊,即所定義的模塊不依賴其他模塊;第二種情況是定義非獨立模塊,即所定義的模塊依賴于其他模塊。 **(1)獨立模塊** 如果被定義的模塊是一個獨立模塊,不需要依賴任何其他模塊,可以直接用define方法生成。 ```javascript define({ method1: function() {}, method2: function() {}, }); ``` 上面代碼生成了一個擁有method1、method2兩個方法的模塊。 另一種等價的寫法是,把對象寫成一個函數,該函數的返回值就是輸出的模塊。 ```javascript define(function () { return { method1: function() {}, method2: function() {}, }; }); ``` 后一種寫法的自由度更高一點,可以在函數體內寫一些模塊初始化代碼。 值得指出的是,define定義的模塊可以返回任何值,不限于對象。 **(2)非獨立模塊** 如果被定義的模塊需要依賴其他模塊,則define方法必須采用下面的格式。 ```javascript define(['module1', 'module2'], function(m1, m2) { ... }); ``` define方法的第一個參數是一個數組,它的成員是當前模塊所依賴的模塊。比如,['module1', 'module2']表示我們定義的這個新模塊依賴于module1模塊和module2模塊,只有先加載這兩個模塊,新模塊才能正常運行。一般情況下,module1模塊和module2模塊指的是,當前目錄下的module1.js文件和module2.js文件,等同于寫成['./module1', './module2']。 define方法的第二個參數是一個函數,當前面數組的所有成員加載成功后,它將被調用。它的參數與數組的成員一一對應,比如function(m1, m2)就表示,這個函數的第一個參數m1對應module1模塊,第二個參數m2對應module2模塊。這個函數必須返回一個對象,供其他模塊調用。 ```javascript define(['module1', 'module2'], function(m1, m2) { return { method: function() { m1.methodA(); m2.methodB(); } }; }); ``` 上面代碼表示新模塊返回一個對象,該對象的method方法就是外部調用的接口,menthod方法內部調用了m1模塊的methodA方法和m2模塊的methodB方法。 需要注意的是,回調函數必須返回一個對象,這個對象就是你定義的模塊。 如果依賴的模塊很多,參數與模塊一一對應的寫法非常麻煩。 ```javascript define( [ 'dep1', 'dep2', 'dep3', 'dep4', 'dep5', 'dep6', 'dep7', 'dep8'], function(dep1, dep2, dep3, dep4, dep5, dep6, dep7, dep8){ ... } ); ``` 為了避免像上面代碼那樣繁瑣的寫法,RequireJS提供一種更簡單的寫法。 ```javascript define( function (require) { var dep1 = require('dep1'), dep2 = require('dep2'), dep3 = require('dep3'), dep4 = require('dep4'), dep5 = require('dep5'), dep6 = require('dep6'), dep7 = require('dep7'), dep8 = require('dep8'); ... } }); ``` 下面是一個define實際運用的例子。 ```javascript define(['math', 'graph'], function ( math, graph ) { return { plot: function(x, y){ return graph.drawPie(math.randomGrid(x,y)); } } }; ); ``` 上面代碼定義的模塊依賴math和graph兩個庫,然后返回一個具有plot接口的對象。 另一個實際的例子是,通過判斷瀏覽器是否為IE,而選擇加載zepto或jQuery。 ```javascript define(('__proto__' in {} ? ['zepto'] : ['jquery']), function($) { return $; }); ``` 上面代碼定義了一個中間模塊,該模塊先判斷瀏覽器是否支持__proto__屬性(除了IE,其他瀏覽器都支持),如果返回true,就加載zepto庫,否則加載jQuery庫。 ### require方法:調用模塊 require方法用于調用模塊。它的參數與define方法類似。 ```javascript require(['foo', 'bar'], function ( foo, bar ) { foo.doSomething(); }); ``` 上面方法表示加載foo和bar兩個模塊,當這兩個模塊都加載成功后,執行一個回調函數。該回調函數就用來完成具體的任務。 require方法的第一個參數,是一個表示依賴關系的數組。這個數組可以寫得很靈活,請看下面的例子。 ```javascript require( [ window.JSON ? undefined : 'util/json2' ], function ( JSON ) { JSON = JSON || window.JSON; console.log( JSON.parse( '{ "JSON" : "HERE" }' ) ); }); ``` 上面代碼加載JSON模塊時,首先判斷瀏覽器是否原生支持JSON對象。如果是的,則將undefined傳入回調函數,否則加載util目錄下的json2模塊。 require方法也可以用在define方法內部。 ```javascript define(function (require) { var otherModule = require('otherModule'); }); ``` 下面的例子顯示了如何動態加載模塊。 ```javascript define(function ( require ) { var isReady = false, foobar; require(['foo', 'bar'], function (foo, bar) { isReady = true; foobar = foo() + bar(); }); return { isReady: isReady, foobar: foobar }; }); ``` 上面代碼所定義的模塊,內部加載了foo和bar兩個模塊,在沒有加載完成前,isReady屬性值為false,加載完成后就變成了true。因此,可以根據isReady屬性的值,決定下一步的動作。 下面的例子是模塊的輸出結果是一個promise對象。 ```javascript define(['lib/Deferred'], function( Deferred ){ var defer = new Deferred(); require(['lib/templates/?index.html','lib/data/?stats'], function( template, data ){ defer.resolve({ template: template, data:data }); } ); return defer.promise(); }); ``` 上面代碼的define方法返回一個promise對象,可以在該對象的then方法,指定下一步的動作。 如果服務器端采用JSONP模式,則可以直接在require中調用,方法是指定JSONP的callback參數為define。 ```javascript require( [ "http://someapi.com/foo?callback=define" ], function (data) { console.log(data); }); ``` require方法允許添加第三個參數,即錯誤處理的回調函數。 ```javascript require( [ "backbone" ], function ( Backbone ) { return Backbone.View.extend({ /* ... */ }); }, function (err) { // ... } ); ``` require方法的第三個參數,即處理錯誤的回調函數,接受一個error對象作為參數。 require對象還允許指定一個全局性的Error事件的監聽函數。所有沒有被上面的方法捕獲的錯誤,都會被觸發這個監聽函數。 ```javascript requirejs.onError = function (err) { // ... }; ``` ### AMD模式小結 define和require這兩個定義模塊、調用模塊的方法,合稱為AMD模式。它的模塊定義的方法非常清晰,不會污染全局環境,能夠清楚地顯示依賴關系。 AMD模式可以用于瀏覽器環境,并且允許非同步加載模塊,也可以根據需要動態加載模塊。 ## 配置require.js:config方法 require方法本身也是一個對象,它帶有一個config方法,用來配置require.js運行參數。config方法接受一個對象作為參數。 ```javascript require.config({ paths: { jquery: [ '//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js', 'lib/jquery' ] } }); ``` config方法的參數對象有以下主要成員: **(1)paths** paths參數指定各個模塊的位置。這個位置可以是同一個服務器上的相對位置,也可以是外部網址。可以為每個模塊定義多個位置,如果第一個位置加載失敗,則加載第二個位置,上面的示例就表示如果CDN加載失敗,則加載服務器上的備用腳本。需要注意的是,指定本地文件路徑時,可以省略文件最后的js后綴名。 ```javascript require(["jquery"], function($) { // ... }); ``` 上面代碼加載jquery模塊,因為jquery的路徑已經在paths參數中定義了,所以就會到事先設定的位置下載。 **(2)baseUrl** baseUrl參數指定本地模塊位置的基準目錄,即本地模塊的路徑是相對于哪個目錄的。該屬性通常由require.js加載時的data-main屬性指定。 **(3)shim** 有些庫不是AMD兼容的,這時就需要指定shim屬性的值。shim可以理解成“墊片”,用來幫助require.js加載非AMD規范的庫。 ```javascript require.config({ paths: { "backbone": "vendor/backbone", "underscore": "vendor/underscore" }, shim: { "backbone": { deps: [ "underscore" ], exports: "Backbone" }, "underscore": { exports: "_" } } }); ``` 上面代碼中的backbone和underscore就是非AMD規范的庫。shim指定它們的依賴關系(backbone依賴于underscore),以及輸出符號(backbone為“Backbone”,underscore為“_”)。 ## 插件 RequireJS允許使用插件,加載各種格式的數據。完整的插件清單可以查看[官方網站](https://github.com/jrburke/requirejs/wiki/Plugins)。 下面是插入文本數據所使用的text插件的例子。 ```javascript define([ 'backbone', 'text!templates.html' ], function( Backbone, template ){ // ... }); ``` 上面代碼加載的第一個模塊是backbone,第二個模塊則是一個文本,用'text!'表示。該文本作為字符串,存放在回調函數的template變量中。 ## 優化器r.js RequireJS提供一個基于node.js的命令行工具r.js,用來壓縮多個js文件。它的主要作用是將多個模塊文件壓縮合并成一個腳本文件,以減少網頁的HTTP請求數。 第一步是安裝r.js(假設已經安裝了node.js)。 ```bash npm install -g requirejs ``` 然后,使用的時候,直接在命令行鍵入以下格式的命令。 ```bash node r.js -o <arguments> ``` &lt;argument&gt;表示命令運行時,所需要的一系列參數,比如像下面這樣: ```bash node r.js -o baseUrl=. name=main out=main-built.js ``` 除了直接在命令行提供參數設置,也可以將參數寫入一個文件,假定文件名為build.js。 ```javascript ({ baseUrl: ".", name: "main", out: "main-built.js" }) ``` 然后,在命令行下用r.js運行這個參數文件,就OK了,不需要其他步驟了。 ```bash node r.js -o build.js ``` 下面是一個參數文件的范例,假定位置就在根目錄下,文件名為build.js。 ```javascript ({ appDir: './', baseUrl: './js', dir: './dist', modules: [ { name: 'main' } ], fileExclusionRegExp: /^(r|build)\.js$/, optimizeCss: 'standard', removeCombined: true, paths: { jquery: 'lib/jquery', underscore: 'lib/underscore', backbone: 'lib/backbone/backbone', backboneLocalstorage: 'lib/backbone/backbone.localStorage', text: 'lib/require/text' }, shim: { underscore: { exports: '_' }, backbone: { deps: [ 'underscore', 'jquery' ], exports: 'Backbone' }, backboneLocalstorage: { deps: ['backbone'], exports: 'Store' } } }) ``` 上面代碼將多個模塊壓縮合并成一個main.js。 參數文件的主要成員解釋如下: - **appDir**:項目目錄,相對于參數文件的位置。 - **baseUrl**:js文件的位置。 - **dir**:輸出目錄。 - **modules**:一個包含對象的數組,每個對象就是一個要被優化的模塊。 - **fileExclusionRegExp**:凡是匹配這個正則表達式的文件名,都不會被拷貝到輸出目錄。 - **optimizeCss**: 自動壓縮CSS文件,可取的值包括“none”, “standard”, “standard.keepLines”, “standard.keepComments”, “standard.keepComments.keepLines”。 - **removeCombined**:如果為true,合并后的原文件將不保留在輸出目錄中。 - **paths**:各個模塊的相對路徑,可以省略js后綴名。 - **shim**:配置依賴性關系。如果某一個模塊不是AMD模式定義的,就可以用shim屬性指定模塊的依賴性關系和輸出值。 - **generateSourceMaps**:是否要生成source map文件。 更詳細的解釋可以參考[官方文檔](https://github.com/jrburke/r.js/blob/master/build/example.build.js)。 運行優化命令后,可以前往dist目錄查看優化后的文件。 下面是另一個build.js的例子。 ```javascript ({ mainConfigFile : "js/main.js", baseUrl: "js", removeCombined: true, findNestedDependencies: true, dir: "dist", modules: [ { name: "main", exclude: [ "infrastructure" ] }, { name: "infrastructure" } ] }) ``` 上面代碼將模塊文件壓縮合并成兩個文件,第一個是main.js(指定排除infrastructure.js),第二個則是infrastructure.js。 <h2 id="10.11">Lint 工具</h2> ## 概述 Lint工具用于檢查代碼的語法是否正確、風格是否符合要求。 JavaScript語言的最早的Lint工具,是Douglas Crockford開發的JSLint。由于該工具所有的語法規則,都是預設的,用戶無法改變。所以,很快就有人抱怨,JSLint不是讓你寫成正確的JavaScript,而是讓你像Douglas Crockford一樣寫JavaScript。 JSHint可以看作是JSLint的后繼者,最大特定就是允許用戶自定義自己的語法規則,寫在項目根目錄下面的`.jshintrc`文件。 JSLint和JSHint同時檢查你的語法和風格。另一個工具JSCS則是只檢查語法風格。 最新的工具ESLint不僅允許你自定義語法規則,還允許用戶創造插件,改變默認的JavaScript語法,比如支持ES6和JSX的語法。 ## ESLint ### 基本用法 首先,安裝ESLint。 ```bash $ npm i -g eslint ``` 其次,在項目根目錄下面新建一個`.eslintrc`文件,里面定義了你的語法規則。 ```javascript { "rules": { "indent": 2, "no-unused-vars": 2, "no-alert": 1 }, "env": { "browser": true } } ``` 上面的`.eslintrc`文件是JSON格式,里面首先定義,這些規則只適用于瀏覽器環境。如果要定義,同時適用于瀏覽器環境和Node環境,可以寫成下面這樣。 ```javascript { "env": { "browser": true, "node": true } } ``` 然后,上面的`.eslintrc`文件定義了三條語法規則。每個語法規則后面,表示這個規則的級別。 - 0:關閉該條規則。 - 1:違反這條規則,會拋出一個警告。 - 2:違反這條規則,會拋出一個錯誤。 接下來,新建一個`index.js`文件。 ```javascript var unusued = 'I have no purpose!'; function greet() { var message = 'Hello, World!'; alert(message); } greet(); ``` 然后,運行ESLint檢查該文件,結果如下。 ```bash $ eslint index.js index.js 1:5 error unusued is defined but never used no-unused-vars 5:5 warning Unexpected alert no-alert ? 2 problems (1 error, 1 warning) ``` 上面代碼檢查出兩個問題,一個是定義了變量卻沒有使用,二是存在alert。 ### 預置規則 自己設置所有語法規則,是非常麻煩的。所以,ESLint提供了預設的語法樣式,比較常用的Airbnb的語法規則。由于這個規則集涉及ES6,所以還需要安裝Babel插件。 ```bash $ npm i -g babel-eslint eslint-config-airbnb ``` 安裝完成后,在`.eslintrc`文件中注明,使用Airbnb語法規則。 ```bash { "extends": "eslint-config-airbnb" } ``` 你也可以用自己的規則,覆蓋預設的語法規則。 ```javascript { "extends": "eslint-config-airbnb", "rules": { "no-var": 0, "no-alert": 0 } } ``` ### 語法規則 (1)indent indent規則設定行首的縮進,默認是四個空格。下面的幾種寫法,可以改變這個設置。 ```javascript // 縮進為4個空格(默認值) "indent": 2 // 縮進為2個空格 "indent": [2, 2] // 縮進為1個tab鍵 "indent": [2, "tab"] // 縮進為2個空格, // 同時,switch...case結構的case也必須縮進,默認是不打開的 "indent": [2, 2, {"SwitchCase": 1}] ``` (2)no-unused-vars 不允許聲明了變量,卻不使用。 ```javascript "no-unused-vars": [2, {"vars": "local", "args": "after-used"}] ``` 上面代碼中,vars字段表示只檢查局部變量,允許全局變量聲明了卻不使用;args字段表示函數的參數,只要求使用最后一個參數,前面的參數可以不使用。 (3)no-alert 不得使用alert、confirm和prompt。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看