obile 應用程序是建立在您想要顯示的簡單的點擊事物上。
在 jQuery Mobile 中創建按鈕
在 jQuery Mobile 中,按鈕可通過三種方式創建:
使用 <button> 元素
使用 <input> 元素
使用帶有 data-role="button" 的 <a> 元素
<button>
<button>按鈕</button>
嘗試一下 ?
<input>
<input type="button" value="按鈕">
嘗試一下 ?
<a>
<a href="#" data-role="button">按鈕</a>
嘗試一下 ?
在 jQuery Mobile 中,按鈕會自動樣式化,讓它們在移動設備上更具吸引力和可用性。我們推薦您使用帶有 data-role="button" 的 <a> 元素在頁面間進行鏈接,使用 <input> 或 <button> 元素進行表單提交。 | ||
默認情況下,組合按鈕是垂直組合,它們之間沒有外邊距和空間。并且只有第一個和最后一個按鈕是圓角,以便它們組合在一起的時候創建一個漂亮的外觀。 |
類 | 描述 | 實例 |
---|---|---|
ui-btn-b | 修改按鈕顏色為黑色,字體為白色(默認為灰色背景,黑色字體)。 | 嘗試一下 |
ui-corner-all | 為按鈕添加圓角 | 嘗試一下 |
ui-mini | 制作小按鈕 | 嘗試一下 |
ui-shadow | 為按鈕添加陰影 | 嘗試一下 |
默認情況下 <input> 按鈕有圓角及陰影效果。 <a> 和 <button> 元素沒有。
果你是 JavaScript 的新手,一些像 “module bundlers vs module loaders”、“Webpack vs Browserify” 和 “AMD vs.CommonJS” 這樣的術語,很快讓你不堪重負。
JavaScript 模塊系統可能令人生畏,但理解它對 Web 開發人員至關重要。
在這篇文章中,我將以簡單的言語(以及一些代碼示例)為你解釋這些術語。 希望這對你有會有幫助!
好作者能將他們的書分成章節,優秀的程序員將他們的程序劃分為模塊。
就像書中的章節一樣,模塊只是文字片段(或代碼,視情況而定)的集群。然而,好的模塊是高內聚低松耦的,具有不同的功能,允許在必要時對它們進行替換、刪除或添加,而不會擾亂整體功能。
使用模塊有利于擴展、相互依賴的代碼庫,這有很多好處。在我看來,最重要的是:
1)可維護性: 根據定義,模塊是高內聚的。一個設計良好的模塊旨在盡可能減少對代碼庫部分的依賴,這樣它就可以獨立地增強和改進,當模塊與其他代碼片段解耦時,更新單個模塊要容易得多。
回到我們的書的例子,如果你想要更新你書中的一個章節,如果對一個章節的小改動需要你調整每一個章節,那將是一場噩夢。相反,你希望以這樣一種方式編寫每一章,即可以在不影響其他章節的情況下進行改進。
2)命名空間: 在 JavaScript 中,頂級函數范圍之外的變量是全局的(這意味著每個人都可以訪問它們)。因此,“名稱空間污染”很常見,完全不相關的代碼共享全局變量。
在不相關的代碼之間共享全局變量在開發中是一個大禁忌。正如我們將在本文后面看到的,通過為變量創建私有空間,模塊允許我們避免名稱空間污染。
3)可重用性:坦白地說:我們將前寫過的代碼復制到新項目中。 例如,假設你從之前項目編寫的一些實用程序方法復制到當前項目中。
這一切都很好,但如果你找到一個更好的方法來編寫代碼的某些部分,那么你必須記得回去在曾經使用過的其他項目更新它。
這顯然是在浪費時間。如果有一個我們可以一遍又一遍地重復使用的模塊,不是更容易嗎?
有多種方法來創建模塊,來看幾個:
模塊模式用于模擬類的概念(因為 JavaScript 本身不支持類),因此我們可以在單個對象中存儲公共和私有方法和變量——類似于在 Java 或 Python 等其他編程語言中使用類的方式。這允許我們為想要公開的方法創建一個面向公共的 API,同時仍然將私有變量和方法封裝在閉包范圍中。
有幾種方法可以實現模塊模式。在第一個示例中,將使用匿名閉包,將所有代碼放在匿名函數中來幫助我們實現目標。(記住:在 JavaScript 中,函數是創建新作用域的唯一方法。)
例一:匿名閉包
(function () {
// 將這些變量放在閉包范圍內實現私有化
var myGrades=[93, 95, 88, 0, 55, 91];
var average=function() {
var total=myGrades.reduce(function(accumulator, item) {
return accumulator + item}, 0);
return '平均分 ' + total / myGrades.length + '.';
}
var failing=function(){
var failingGrades=myGrades.filter(function(item) {
return item < 70;});
return '掛機科了 ' + failingGrades.length + ' 次。';
}
console.log(failing()); // 掛機科了次
}());
使用這個結構,匿名函數就有了自己的執行環境或“閉包”,然后我們立即執行。這讓我們可以從父(全局)命名空間隱藏變量。
這種方法的優點是,你可以在這個函數中使用局部變量,而不會意外地覆蓋現有的全局變量,但仍然可以訪問全局變量,就像這樣:
var global='你好,我是一個全局變量。)';
(function () {
// 將這些變量放在閉包范圍內實現私有化
var myGrades=[93, 95, 88, 0, 55, 91];
var average=function() {
var total=myGrades.reduce(function(accumulator, item) {
return accumulator + item}, 0);
return '平均分 ' + total / myGrades.length + '.';
}
var failing=function(){
var failingGrades=myGrades.filter(function(item) {
return item < 70;});
return '掛機科了 ' + failingGrades.length + ' 次。';
}
console.log(failing()); // 掛機科了次
onsole.log(global); // 你好,我是一個全局變量。
}());
注意,匿名函數的圓括號是必需的,因為以關鍵字 function 開頭的語句通常被認為是函數聲明(請記住,JavaScript 中不能使用未命名的函數聲明)。因此,周圍的括號將創建一個函數表達式,并立即執行這個函數,這還有另一種叫法 立即執行函數(IIFE)。如果你對這感興趣,可以在這里了解到更多。
例二:全局導入
jQuery 等庫使用的另一種流行方法是全局導入。它類似于我們剛才看到的匿名閉包,只是現在我們作為參數傳入全局變量:
(function (globalVariable) {
// 在這個閉包范圍內保持變量的私有化
var privateFunction=function() {
console.log('Shhhh, this is private!');
}
// 通過 globalVariable 接口公開下面的方法
// 同時將方法的實現隱藏在 function() 塊中
globalVariable.each=function(collection, iterator) {
if (Array.isArray(collection)) {
for (var i=0; i < collection.length; i++) {
iterator(collection[i], i, collection);
}
} else {
for (var key in collection) {
iterator(collection[key], key, collection);
}
}
};
globalVariable.filter=function(collection, test) {
var filtered=[];
globalVariable.each(collection, function(item) {
if (test(item)) {
filtered.push(item);
}
});
return filtered;
};
globalVariable.map=function(collection, iterator) {
var mapped=[];
globalUtils.each(collection, function(value, key, collection) {
mapped.push(iterator(value));
});
return mapped;
};
globalVariable.reduce=function(collection, iterator, accumulator) {
var startingValueMissing=accumulator===undefined;
globalVariable.each(collection, function(item) {
if(startingValueMissing) {
accumulator=item;
startingValueMissing=false;
} else {
accumulator=iterator(accumulator, item);
}
});
return accumulator;
};
}(globalVariable));
在這個例子中,globalVariable 是唯一的全局變量。與匿名閉包相比,這種方法的好處是可以預先聲明全局變量,使得別人更容易閱讀代碼。
例三:對象接口
另一種方法是使用立即執行函數接口對象創建模塊,如下所示:
var myGradesCalculate=(function () {
// 將這些變量放在閉包范圍內實現私有化
var myGrades=[93, 95, 88, 0, 55, 91];
// 通過接口公開這些函數,同時將模塊的實現隱藏在function()塊中
return {
average: function() {
var total=myGrades.reduce(function(accumulator, item) {
return accumulator + item;
}, 0);
return'平均分 ' + total / myGrades.length + '.';
},
failing: function() {
var failingGrades=myGrades.filter(function(item) {
return item < 70;
});
return '掛科了' + failingGrades.length + ' 次.';
}
}
})();
myGradesCalculate.failing(); // '掛科了 2 次.'
myGradesCalculate.average(); // '平均分 70.33333333333333.'
正如您所看到的,這種方法允許我們通過將它們放在 return 語句中(例如算平均分和掛科數方法)來決定我們想要保留的變量/方法(例如 myGrades)以及我們想要公開的變量/方法。
例四:顯式模塊模式
這與上面的方法非常相似,只是它確保所有方法和變量在顯式公開之前都是私有的:
var myGradesCalculate=(function () {
// 將這些變量放在閉包范圍內實現私有化
var myGrades=[93, 95, 88, 0, 55, 91];
var average=function() {
var total=myGrades.reduce(function(accumulator, item) {
return accumulator + item;
}, 0);
return'平均分 ' + total / myGrades.length + '.';
};
var failing=function() {
var failingGrades=myGrades.filter(function(item) {
return item < 70;
});
return '掛科了' + failingGrades.length + ' 次.';
};
// Explicitly reveal public pointers to the private functions
// that we want to reveal publicly
return {
average: average,
failing: failing
}
})();
myGradesCalculate.failing(); // '掛科了 2 次.'
myGradesCalculate.average(); // '平均分 70.33333333333333.'
這可能看起來很多,但它只是模塊模式的冰山一角。 以下是我在自己的探索中發現有用的一些資源:
所有這些方法都有一個共同點:使用單個全局變量將其代碼包裝在函數中,從而使用閉包作用域為自己創建一個私有名稱空間。
雖然每種方法都有效且都有各自特點,但卻都有缺點。
首先,作為開發人員,你需要知道加載文件的正確依賴順序。例如,假設你在項目中使用 Backbone,因此你可以將 Backbone 的源代碼 以<script> 腳本標簽的形式引入到文件中。
但是,由于 Backbone 對 Underscore.js 有很強的依賴性,因此 Backbone 文件的腳本標記不能放在Underscore.js 文件之前。
作為一名開發人員,管理依賴關系并正確處理這些事情有時會令人頭痛。
另一個缺點是它們仍然會導致名稱空間沖突。例如,如果兩個模塊具有相同的名稱怎么辦?或者,如果有一個模塊的兩個版本,并且兩者都需要,該怎么辦?
幸運的是,答案是肯定的。
有兩種流行且實用的方法:CommonJS 和 AMD。
CommonJS 是一個志愿者工作組,負責設計和實現用于聲明模塊的 JavaScript API。
CommonJS 模塊本質上是一個可重用的 JavaScript,它導出特定的對象,使其可供其程序中需要的其他模塊使用。 如果你已經使用 Node.js 編程,那么你應該非常熟悉這種格式。
使用 CommonJS,每個 JavaScript 文件都將模塊存儲在自己獨立的模塊上下文中(就像將其封裝在閉包中一樣)。 在此范圍內,我們使用 module.exports 導出模塊,或使用 require 來導入模塊。
在定義 CommonJS 模塊時,它可能是這樣的:
function myModule() {
this.hello=function() {
return 'hello!';
}
this.goodbye=function() {
return 'goodbye!';
}
}
module.exports=myModule;
我們使用特殊的對象模塊,并將函數的引用放入 module.exports 中。這讓 CommonJS 模塊系統知道我們想要公開什么,以便其他文件可以使用它。
如果想使用 myModule,只需要使用 require 方法就可以,如下:
var myModule=require('myModule');
var myModuleInstance=new myModule();
myModuleInstance.hello(); // 'hello!'
myModuleInstance.goodbye(); // 'goodbye!'
與前面討論的模塊模式相比,這種方法有兩個明顯的好處:
另外需要注意的是,CommonJS 采用服務器優先方法并同步加載模塊。 這很重要,因為如果我們需要三個其他模塊,它將逐個加載它們。
現在,它在服務器上運行良好,但遺憾的是,在為瀏覽器編寫 JavaScript 時使用起來更加困難。 可以這么說,從網上讀取模塊比從磁盤讀取需要更長的時間。 只要加載模塊的腳本正在運行,它就會阻止瀏覽器運行其他任何內容,直到完成加載,這是因為 JavaScript 是單線程且 CommonJS 是同步加載的。
CommonJS一切都很好,但是如果我們想要異步加載模塊呢? 答案是 異步模塊定義,簡稱 AMD。
使用 AMD 的加載模塊如下:
define(['myModule', 'myOtherModule'], function(myModule, myOtherModule) {
console.log(myModule.hello());
});
define 函數的第一個參數是一個數組,數組中是依賴的各種模塊。這些依賴模塊在后臺(以非阻塞的方式)加載進來,一旦加載完畢,define 函數就會調用第二個參數,即回調函數執行操作。
接下來,回調函數接收參數,即依賴模塊 - 示例中就是 myModule 和 myOtherModule - 允許函數使用這些依賴項, 最后,所依賴的模塊本身也必須使用 define 關鍵字來定義。例如,myModule如下所示:
define([], function() {
return {
hello: function() {
console.log('hello');
},
goodbye: function() {
console.log('goodbye');
}
};
});
因此,與 CommonJS 不同,AMD 采用瀏覽器優先的方法和異步行為來完成工作。 (注意,有很多人堅信在開始運行代碼時動態加載文件是不利的,我們將在下一節關于模塊構建的內容中探討更多內容)。
除了異步性,AMD 的另一個好處是模塊可以是對象,函數,構造函數,字符串,JSON 和許多其他類型,而CommonJS 只支持對象作為模塊。
也就是說,和CommonJS相比,AMD不兼容io、文件系統或者其他服務器端的功能特性,而且函數包裝語法與簡單的require 語句相比有點冗長。
對于同時支持 AMD 和 CommonJS 特性的項目,還有另一種格式:通用模塊定義(Universal Module Definition, UMD)。
UMD 本質上創造了一種使用兩者之一的方法,同時也支持全局變量定義。因此,UMD 模塊能夠同時在客戶端和服務端同時工作。
簡單看一下 UMD 是怎樣工作的:
(function (root, factory) {
if (typeof define==='function' && define.amd) {
// AMD
define(['myModule', 'myOtherModule'], factory);
} else if (typeof exports==='object') {
// CommonJS
module.exports=factory(require('myModule'), require('myOtherModule'));
} else {
// Browser globals (Note: root is window)
root.returnExports=factory(root.myModule, root.myOtherModule);
}
}(this, function (myModule, myOtherModule) {
// Methods
function notHelloOrGoodbye(){}; // A private method
function hello(){}; // A public method because it's returned (see below)
function goodbye(){}; // A public method because it's returned (see below)
// Exposed public methods
return {
hello: hello,
goodbye: goodbye
}
}));
Github 上 enlightening repo 里有更多關于 UMD 的例子。
你可能已經注意到,上面的模塊都不是 JavaScript 原生的。相反,我們已經創建了通過使用模塊模式、CommonJS 或 AMD 來模擬模塊系統的方法。
幸運的是,TC39(定義 ECMAScript 的語法和語義的標準組織)一幫聰明的人已經引入了ECMAScript 6(ES6)的內置模塊。
ES6 為導入導出模塊提供了很多不同的可能性,已經有許多其他人花時間解釋這些,下面是一些有用的資源:
與 CommonJS 或 AMD 相比,ES6 模塊最大的優點在于它能夠同時提供兩方面的優勢:簡明的聲明式語法和異步加載,以及對循環依賴項的更好支持。
也許我個人最喜歡的 ES6 模塊功能是它的導入模塊是導出時模塊的實時只讀視圖。(相比起 CommonJS,導入的是導出模塊的拷貝副本,因此也不是實時的)。
下面是一個例子:
// lib/counter.js
var counter=1;
function increment() {
counter++;
}
function decrement() {
counter--;
}
module.exports={
counter: counter,
increment: increment,
decrement: decrement
};
// src/main.js
var counter=require('../../lib/counter');
counter.increment();
console.log(counter.counter); // 1
在這個例子中,我們基本上創建了兩個模塊的對象:一個用于導出它,一個在我們需要的時候引入。
此外,在 main.js 中的對象目前是與原始模塊是相互獨立的,這就是為什么即使我們執行 increment 方法,它仍然返回 1,因為引入的變量和最初導入的變量是毫無關聯的。需要改變你引入的對象唯一的方式是手動執行增加:
counter.counter++;
console.log(counter.counter); // 2
另一方面,ES6創建了我們導入的模塊的實時只讀視圖:
// lib/counter.js
export let counter=1;
export function increment() {
counter++;
}
export function decrement() {
counter--;
}
// src/main.js
import * as counter from '../../counter';
console.log(counter.counter); // 1
counter.increment();
console.log(counter.counter); // 2
超酷?我發現這一點是因為ES6允許你可以把你定義的模塊拆分成更小的模塊而不用刪減功能,然后你還能反過來把它們合成到一起, 完全沒問題。
總體上看,模塊打包只是將一組模塊(及其依賴項)以正確的順序拼接到一個文件(或一組文件)中的過程。正如 Web開發的其它方方面面,棘手的問題總是潛藏在具體的細節里。
將程序劃分為模塊時,通常會將這些模塊組織到不同的文件和文件夾中。 有可能,你還有一組用于正在使用的庫的模塊,如 Underscore 或 React。
因此,每個文件都必須以一個 <script> 標簽引入到主 HTML 文件中,然后當用戶訪問你的主頁時由瀏覽器加載進來。 每個文件使用 <script> 標簽引入,意味著瀏覽器不得不分別逐個的加載它們。
這對于頁面加載時間來說簡直是噩夢。
為了解決這個問題,我們將所有文件打包或“拼接”到一個大文件(或視情況而定的幾個文件),以減少請求的數量。 當你聽到開發人員談論“構建步驟”或“構建過程”時,這就是他們所談論的內容。
另一種加速構建操作的常用方法是“縮減”打包代碼。 縮減是從源代碼中移除不必要的字符(例如,空格,注釋,換行符等)的過程,以便在不改變代碼功能的情況下減少內容的整體大小。
較少的數據意味著瀏覽器處理時間會更快,從而減少了下載文件所需的時間。 如果你見過具有 “min” 擴展名的文件,如 “underscore-min.js” ,可能會注意到與完整版相比,縮小版本非常小(不過很難閱讀)。
除了捆綁和/或加載模塊之外,模塊捆綁器還提供了許多其他功能,例如在進行更改時生成自動重新編譯代碼或生成用于調試的源映射。
構建工具(如 Gulp 和 Grunt)能為開發者直接進行拼接和縮減,確保為開發人員提供可讀代碼,同時有利于瀏覽器執行的代碼。
當你使用一種標準模塊模式(上部分討論過)來定義模塊時,拼接和縮減文件非常有用。 你真正在做的就是將一堆普通的 JavaScript 代碼捆綁在一起。
但是,如果你堅持使用瀏覽器無法解析的非原生模塊系統(如 CommonJS 或 AMD(甚至是原生 ES6模塊格式)),則需要使用專門工具將模塊轉換為排列正確、瀏覽器可解析的代碼。 這就是 Browserify,RequireJS,Webpack 和其他“模塊打包工具”或“模塊加載工具”的用武之地。
除了打包和/或加載模塊之外,模塊打包器還提供了許多其他功能,例如在進行更改時生成自動重新編譯代碼或生成用于調試的源映射。
下面是一些常見的模塊打包方法:
正如前面所知道的,CommonJS以同步方式加載模塊,這沒有什么問題,只是它對瀏覽器不實用。我提到過有一個解決方案——其中一個是一個名為 Browserify 的模塊打包工具。Browserify 是一個為瀏覽器編譯 CommonJS模塊的工具。
例如,有個 main.js 文件,它導入一個模塊來計算一組數字的平均值:
var myDependency=require(‘myDependency’);
var myGrades=[93, 95, 88, 0, 91];
var myAverageGrade=myDependency.average(myGrades);
在這種情況下,我們有一個依賴項(myDependency),使用下面的命令,Browserify 以 main.js 為入口把所有依賴的模塊遞歸打包成一個文件:
browserify main.js -o bundle.js
Browserify 通過跳入文件分析每一個依賴的 抽象語法樹(AST),以便遍歷項目的整個依賴關系圖。一旦確定了依賴項的結構,就把它們按正確的順序打包到一個文件中。然后,在 html 里插入一個用于引入 “bundle.js” 的 <script> 標簽,從而確保你的源代碼在一個 HTTP 請求中完成下載。
類似地,如果有多個文件且有多個依賴時,只需告訴 Browserify 的入口文件路徑即可。最后打包后的文件可以通過 Minify-JS 之類的工具壓縮打包后的代碼。
如果你正在使用 AMD,你需要使用像 RequireJS 或者 Curl 這樣的 AMD 加載器。模塊加載器(與模塊打包工具不同)會動態加載程序需要運行的模塊。
提醒一下,AMD 與 CommonJS 的主要區別之一是它以異步方式加載模塊。 從這個意義上說,對于 AMD,從技術上講,實際上并不需要構建步驟,因為異步加載模塊意味著在運行過程中逐步下載那些程序所需要的文件,而不是用戶剛進入頁面就一下把所有文件都下載下來。
但實際上,對于每個用戶操作而言,隨著時間的推移,大容量請求的開銷在生產中沒有多大意義。 大多數 Web 開發人員仍然使用構建工具打包和壓縮 AMD 模塊以獲得最佳性能,例如使用 RequireJS 優化器,r.js 等工具。
總的來說,AMD 和 CommonJS 在打包方面的區別在于:在開發期間,AMD 可以省去任何構建過程。當然,在代碼上線前,要使用優化工具(如 r.js)進行優化。
就打包工具而言,Webpack 是一個新事物。它被設計成與你使用的模塊系統無關,允許開發人員在適當的情況下使用 CommonJS、AMD 或 ES6。
你可能想知道,為什么我們需要 Webpack,而我們已經有了其他打包工具了,比如 Browserify 和 RequireJS,它們可以完成工作,并且做得非常好。首先,Webpack 提供了一些有用的特性,比如 “代碼分割”(code splitting) —— 一種將代碼庫分割為“塊(chunks)”的方式,從而能實現按需加載。
例如,如果你的 Web 應用程序,其中只需要某些代碼,那么將整個代碼庫都打包進一個大文件就不是很高效。 在這種情況下,可以使用代碼分割,將需要的部分代碼抽離在"打包塊",在執行按需加載,從而避免在最開始就遇到大量負載的麻煩。
代碼分割只是 Webpack 提供的眾多引人注目的特性之一,網上有很多關于 “Webpack 與 Browserify 誰更好” 的激烈討論。以下是一些客觀冷靜的討論,幫助我稍微理清了頭緒:
當前 JS 模塊規范(CommonJS, AMD) 與 ES6 模塊之間最重要的區別是 ES6 模塊的設計考慮到了靜態分析。這意味著當你導入模塊時,導入的模塊在編譯階段也就是代碼開始運行之前就被解析了。這允許我們在運行程序之前移,移除那些在導出模塊中不被其它模塊使用的部分。移除不被使用的模塊能節省空間,且有效地減少瀏覽器的壓力。
一個常見的問題,使用一些工具,如 Uglify.js ,縮減代碼時,有一個死碼刪除的處理,它和 ES6 移除沒用的模塊又有什么不同呢?只能說 “視情況而定”。
死碼消除(Dead codeelimination)是一種編譯器原理中編譯最優化技術,它的用途是移除對程序運行結果沒有任何影響的代碼。移除這類的代碼有兩種優點,不但可以減少程序的大小,還可以避免程序在運行中進行不相關的運算行為,減少它運行的時間。不會被運行到的代碼(unreachable code)以及只會影響到無關程序運行結果的變量(Dead Variables),都是死碼(Dead code)的范疇。
有時,在 UglifyJS 和 ES6 模塊之間死碼消除的工作方式完全相同,有時則不然。如果你想驗證一下, Rollup’s wiki 里有個很好的示例。
ES6 模塊的不同之處在于死碼消除的不同方法,稱為“tree shaking”。“tree shaking” 本質上是死碼消除反過程。它只包含包需要運行的代碼,而非排除不需要的代碼。來看個例子:
假設有一個帶有多個函數的 utils.js 文件,每個函數都用 ES6 的語法導出:
export function each(collection, iterator) {
if (Array.isArray(collection)) {
for (var i=0; i < collection.length; i++) {
iterator(collection[i], i, collection);
}
} else {
for (var key in collection) {
iterator(collection[key], key, collection);
}
}
}
export function filter(collection, test) {
var filtered=[];
each(collection, function(item) {
if (test(item)) {
filtered.push(item);
}
});
return filtered;
}
export function map(collection, iterator) {
var mapped=[];
each(collection, function(value, key, collection) {
mapped.push(iterator(value));
});
return mapped;
}
export function reduce(collection, iterator, accumulator) {
var startingValueMissing=accumulator===undefined;
each(collection, function(item) {
if(startingValueMissing) {
accumulator=item;
startingValueMissing=false;
} else {
accumulator=iterator(accumulator, item);
}
});
return accumulator;
}
接著,假設我們不知道要在程序中使用什么 utils.js 中的哪個函數,所以我們將上述的所有模塊導入main.js中,如下所示:
import * as Utils from ‘./utils.js’;
最終,我們只用到的 each 方法:
import * as Utils from ‘./utils.js’;
Utils.each([1, 2, 3], function(x) { console.log(x) });
“tree shaken” 版本的 main.js 看起來如下(一旦模塊被加載后):
function each(collection, iterator) {
if (Array.isArray(collection)) {
for (var i=0; i < collection.length; i++) {
iterator(collection[i], i, collection);
}
} else {
for (var key in collection) {
iterator(collection[key], key, collection);
}
}
};
each([1, 2, 3], function(x) { console.log(x) });
注意:只導出我們使用的 each 函數。
同時,如果決定使用 filte r函數而不是每個函數,最終會看到如下的結果:
import * as Utils from ‘./utils.js’;
Utils.filter([1, 2, 3], function(x) { return x===2 });
tree shaken 版本如下:
function each(collection, iterator) {
if (Array.isArray(collection)) {
for (var i=0; i < collection.length; i++) {
iterator(collection[i], i, collection);
}
} else {
for (var key in collection) {
iterator(collection[key], key, collection);
}
}
};
function filter(collection, test) {
var filtered=[];
each(collection, function(item) {
if (test(item)) {
filtered.push(item);
}
});
return filtered;
};
filter([1, 2, 3], function(x) { return x===2 });
此時,each 和 filter 函數都被包含進來。這是因為 filter 在定義時使用了 each。因此也需要導出該函數模塊以保證程序正常運行。
我們知道 ES6 模塊的加載方式與其他模塊格式不同,但我們仍然沒有討論使用 ES6 模塊時的構建步驟。
遺憾的是,因為瀏覽器對 ES6模 塊的原生支持還不夠完善,所以現階段還需要我們做一些補充工作。
下面是幾個在瀏覽器中 構建/轉換 ES6 模塊的方法,其中第一個是目前最常用的方法:
作為 web 開發人員,我們必須經歷很多困難。轉換語法優雅的ES6代碼以便在瀏覽器里運行并不總是容易的。
問題是,什么時候 ES6 模塊可以在瀏覽器中運行而不需要這些開銷?
答案是:“盡快”。
ECMAScript 目前有一個解決方案的規范,稱為 ECMAScript 6 module loader API。簡而言之,這是一個綱領性的、基于 Promise 的 API,它支持動態加載模塊并緩存它們,以便后續導入不會重新加載模塊的新版本。
它看起來如下:
// myModule.js
export class myModule {
constructor() {
console.log('Hello, I am a module');
}
hello() {
console.log('hello!');
}
goodbye() {
console.log('goodbye!');
}
}
// main.js
System.import(‘myModule’).then(function(myModule) {
new myModule.hello();
});
// ‘hello!’
你亦可直接對 script 標簽指定 “type=module” 來定義模塊,如:
<script type="module">
// loads the 'myModule' export from 'mymodule.js'
import { hello } from 'mymodule';
new Hello(); // 'Hello, I am a module!'
</script>
更加詳細的介紹也可以在 Github 上查看:es-module-loader
此外,如果您想測試這種方法,請查看 SystemJS,它建立在 ES6 Module Loader polyfill 之上。 SystemJS 在瀏覽器和 Node 中動態加載任何模塊格式(ES6模塊,AMD,CommonJS 或 全局腳本)。
它跟蹤“模塊注冊表”中所有已加載的模塊,以避免重新加載先前已加載過的模塊。 更不用說它還會自動轉換ES6模塊(如果只是設置一個選項)并且能夠從任何其他類型加載任何模塊類型!
對于日益普及的 ES6 模塊,下面有一些有趣的觀點:
對于 HTTP/1,每個TCP連接只允許一個請求。這就是為什么加載多個資源需要多個請求。有了 HTTP/2,一切都變了。HTTP/2 是完全多路復用的,這意味著多個請求和響應可以并行發生。因此,我們可以在一個連接上同時處理多個請求。
由于每個 HTTP 請求的成本明顯低于HTTP/1,因此從長遠來看,加載一組模塊不會造成很大的性能問題。一些人認為這意味著模塊打包不再是必要的,這當然有可能,但這要具體情況具體分析了。
例如,模塊打包還有 HTTP/2 沒有好處,比如移除冗余的導出模塊以節省空間。 如果你正在構建一個性能至關重要的網站,那么從長遠來看,打包可能會為你帶來增量優勢。 也就是說,如果你的性能需求不是那么極端,那么通過完全跳過構建步驟,可以以最小的成本節省時間。
總的來說,絕大多數網站都用上 HTTP/2 的那個時候離我們現在還很遠。我預測構建過程將會保留,至少在近期內。
一旦 ES6 成為模塊標準,我們還需要其他非原生模塊規范嗎?
我覺得還有。
Web 開發遵守一個標準方法進行導入和導出模塊,而不需要中間構建步驟——網頁開發長期受益于此。但 ES6 成為模塊規范需要多長時間呢?
機會是有,但得等一段時間 。
再者,眾口難調,所以“一個標準的方法”可能永遠不會成為現實。
希望這篇文章能幫你理清一些開發者口中的模塊和模塊打包的相關概念,共進步。
、輸入框
1、字符型輸入框:
(1)字符型輸入框:中文,英文全角、英文半角、數字、空或者空格或者回車、特殊字符(~!@#¥%……&*?[]{}”(特別要注意單引號和&符號))。禁止直接輸入特殊字符時,使用”復制+粘貼”功能嘗試輸入。
(2)長度檢查:最小長度、最大長度、最小長度-1、最大長度+1、輸入超長字符比如把整個文章拷貝過去。
(3)空格檢查:輸入的字符間有空格、字符前有空格、字符后有空格、字符前后有空格
(4)多行文本框輸入:允許回車換行、保存后再顯示能夠保存輸入的格式、僅輸入回車換行,檢查能否正確保存(若能,檢查保存結果,若不能,查看是否有正常提示)
(5)安全性檢查:輸入特殊字符串(null,NULL, ,javascript,<script>,</script>,<title></title>,<html></html>,<td></td>)、輸入腳本函數(<script>alert("abc")</script>)、doucment.write("abc")、<b>hello</b>、sql注入)
2、數值型輸入框:
(1)邊界值:最大值、最小值、最大值+1、最小值-1
(2)位數:最小位數、最大位數、最小位數-1、最大位數+1、輸入超長值
(3)特殊字符:輸入空白(NULL)、空格或"~!@#$%^&*()_+{}|[]\:"<>?;',./?;:'-=等可能導致系統錯誤的字符、禁止直接輸入特殊字符時,嘗試使用粘貼拷貝查看是否能正常提交、word中的特殊功能,通過剪貼板拷貝到輸入框,分頁符,分節符類似公式的上下標等、數值的特殊符號如∑,㏒,㏑,∏,+,-等
(4)異常值:輸入負整數、負小數、分數、輸入字母或漢字、小數(小數前0點舍去的情況,多個小數點的情況)、首位為0的數字如01、02、科學計數法是否支持1.0E2、全角數字與半角數字、數字與字母混合、16進制,8進制數值、貨幣型輸入(允許小數點后面幾位)
(5)安全性檢查:不能直接輸入就copy,輸入內容如上
3、日期型輸入框:
(1)合法性檢查:(輸入0日、1日、32日)、月輸入[1、3、5、7、8、10、12]、日輸入[31]、月輸入[4、6、9、11]、日輸入[30][31]、輸入非閏年,月輸入[2],日期輸入[28、29]、輸入閏年,月輸入[2]、日期輸入[29、30]、月輸入[0、1、12、13]
(2)異常值、特殊字符:輸入空白或NULL、輸入~!@#¥%……&*(){}[]等可能導致系統錯誤的字符
(3)安全性檢查:不能直接輸入,就copy,是否數據檢驗出錯
4、信息重復:在一些需要命名,且名字應該唯一的信息輸入重復的名字或ID,看系統有沒有處理,會否報錯,重名包括是否區分大小寫,以及在輸入內容的前后輸入空格,系統是否作出正確處理
二、搜索功能
若查詢條件為輸入框,則參考輸入框對應類型的測試方法
1、功能實現:
(1)如果支持模糊查詢,搜索名稱中任意一個字符是否能搜索到
(2)比較長的名稱是否能查到
(3)輸入系統中不存在的與之匹配的條件
(4)用戶進行查詢操作時,一般情況是不進行查詢條件的清空,除非需求特殊說明。
(5)拼音查詢
2、組合測試:
(1)不同查詢條件之間來回選擇,是否出現頁面錯誤(單選框和多選框最容易出錯)
(2)測試多個查詢條件時,要注意查詢條件的組合測試,可能不同組合的測試會報錯。
三、添加、修改功能
1、特殊鍵:(1)是否支持Tab鍵 (2)是否支持回車鍵
2、提示信息:(1)不符合要求的地方是否有錯誤提示
3、唯一性:(1)字段唯一的,是否可以重復添加,添加后是否能修改為已存在的字段(字段包括區分大小寫以及在輸入的內容前后輸入空格,保存后,數據是否真的插入到數據庫中,注意保存后數據的正確性)
4、數據正確性:
(1)對編輯頁的每個編輯項進行修改,點擊保存,是否可以保存成功,檢查想關聯的數據是否得到更新。
(2)進行必填項檢查(即是否給出提示以及提示后是否依然把數據存到數據庫中;是否提示后出現頁碼錯亂等)
(3)是否能夠連續添加(針對特殊情況)
(4)在編輯的時候,注意編輯項的長度限制,有時在添加的時候有,在編輯的時候卻沒有(注意要添加和修改規則是否一致)
(5)對于有圖片上傳功能的編輯框,若不上傳圖片,查看編輯頁面時是否顯示有默認的圖片,若上傳圖片,查看是否顯示為上傳圖片
(6)修改后增加數據后,特別要注意查詢頁面的數據是否及時更新,特別是在首頁時要注意數據的更新。
(7)提交數據時,連續多次點擊,查看系統會不會連續增加幾條相同的數據或報錯。
(8)若結果列表中沒有記錄或者沒選擇某條記錄,點擊修改按鈕,系統會拋異常。
四、刪除功能
1、特殊鍵:(1)是否支持Tab鍵 (2)是否支持回車鍵
2、提示信息:(1)不選擇任何信息,直接點擊刪除按鈕,是否有提示(2)刪除某條信息時,應該有確認提示
3、數據 實現:(1)是否能連續刪除多個產品(2)當只有一條數據時,是否可以刪除成功 (3)刪除一條數據后,是否可以添加相同的數據(4)如系統支持批量刪除,注意刪除的信息是否正確 (5)如有全選,注意是否把所有的數據刪除(6)刪除數據時,要注意相應查詢頁面的數據是否及時更新 (7)如刪除的數據與其他業務數據關聯,要注意其關聯性(如刪除部門信息時,部門下游員工,則應該給出提示)(8)如果結果列表中沒有記錄或沒有選擇任何一條記錄,點擊刪除按鈕系統會報錯。
如:某一功能模塊具有最基本的增刪改查功能,則需要進行以下測試
單項功能測試(增加、修改、查詢、刪除)
增加——>增加——>增加 (連續增加測試)
增加——>刪除
增加——>刪除——>增加 (新增加的內容與刪除內容一致)
增加——>修改——>刪除
修改——>修改——>修改 (連續修改測試)
修改——>增加(新增加的內容與修改前內容一致)
修改——>刪除
修改——>刪除——>增加 (新增加的內容與刪除內容一致)
刪除——>刪除——>刪除 (連續刪除測試)
五、注冊、登陸模塊
1、注冊功能:
(1)注冊時,設置密碼為特殊版本號,檢查登錄時是否會報錯
(2)注冊成功后,頁面應該以登陸狀態跳轉到首頁或指定頁面
(3)在注冊成功后,刪除注冊的賬號,檢查是否可以注冊成功
(4)注冊已注冊的賬號,檢查是否可以注冊成功
(5)改變存在的用戶的用戶名和密碼的大小寫,來注冊。(有的需求是區分大小寫,有的不區分)
(6)看是否支持tap和enter鍵等;密碼是否可以復制粘貼;密碼是否以* 之類的加秘符號顯示
(7)對于賬號輸入與密碼輸入運用等價類、邊界值寫就好了,這里不再累述
2、登陸功能:
(1)輸入正確的用戶名和正確的密碼
(2)輸入正確的用戶名和錯誤的密碼
(3)輸入錯誤的用戶名和正確的密碼
(4)輸入錯誤的用戶名和錯誤的密碼
(5)不輸入用戶名和密碼(均為空格)
(6)只輸入用戶名,密碼為空
(7)用戶名為空,只輸入密碼
(8)輸入正確的用戶名和密碼,但是不區分大小寫
(9)用戶名和密碼包括特殊字符
(10)用戶名和密碼輸入超長值
(11)已刪除的用戶名和密碼
(12)登錄時,當頁面刷新或重新輸入數據時,驗證碼是否更新
六、上傳圖片
(1)上傳各種主流的圖片格式文件
(2)上傳非圖片格式的文件
(3)上傳圖片文件的大小,幾k,上百k,到幾兆,幾十兆
(4)上傳正在被其他應用使用的圖片文件
(5)上傳的手動輸入上傳地址
(6)上傳地址輸入不存在的圖片地址
(7)上傳地址輸入圖片名稱來上傳
(8)不選擇文件直接點擊上傳,查看是否給出提示
(9)連續多次選擇不同的文件,查看是否上傳最后一次選擇的文件
(10)同時上傳多張圖片文件
七、查詢結果列表
(1)列表、列寬是否合理
(2)列表數據太寬有沒有提供橫向滾動
(3)列表的列名有沒有與內容對應
(4)列表的每列的列名是否描述的清晰
(5)列表是否把不必要的列都顯示出來
(6)點擊某列進行排序,是否會報錯(點擊查看每一頁的排序是否正確)
(7)雙擊或單擊某列信息,是否會報錯
八、界面和易用性測試
1、風格、樣式、顏色是否協調
2、界面布局是否整齊、協調(保證全部顯示出來的,盡量不要使用滾動條
3、界面操作、標題描述是否恰當(描述有歧義、注意是否有錯別字)4、操作是否符合人們的常規習慣(有沒有把相似的功能的控件放在一起,方便操作)
5、提示界面是否符合規范(不應該顯示英文的cancel、ok,應該顯示中文的確定等)
6、界面中各個控件是否對齊
7、日期控件是否可編輯
8、日期控件的長度是否合理,以修改時可以把時間全部顯示出來為準
9、查詢結果列表列寬是否合理、標簽描述是否合理
10、查詢結果列表太寬沒有橫向滾動提示
11、對于信息比較長的文本,文本框有沒有提供自動豎直滾動條
12、數據錄入控件是否方便
13、有沒有支持Tab鍵,鍵的順序要有條理,不亂跳
14、有沒有提供相關的熱鍵
15、控件的提示語描述是否正確
16、模塊調用是否統一,相同的模塊是否調用同一個界面
17、用滾動條移動頁面時,頁面的控件是否顯示正常
18、日期的正確格式應該是XXXX-XX-XX或XXXX-XX-XX XX:XX:XX
19、頁面是否有多余按鈕或標簽
20、窗口標題或圖標是否與菜單欄的統一
21、窗口的最大化、最小化是否能正確切換
22、對于正常的功能,用戶可以不必閱讀用戶手冊就能使用
23、執行風險操作時,有確認、刪除等提示嗎
24、操作順序是否合理
25、正確性檢查:檢查頁面上的form, button, table, header, footer,提示信息,還有其他文字拼寫,句子的語法等是否正確。
26、系統應該在用戶執行錯誤的操作之前提出警告,提示信息.
27、頁面分辨率檢查,在各種分辨率瀏覽系統檢查系統界面友好性。
28、合理性檢查:做delete, update, add, cancel, back等操作后,查看信息回到的頁面是否合理。
29、檢查本地化是否通過:英文版不應該有中文信息,英文翻譯準確,專業
九、兼容性測試
兼容性測試不只是指界面在不同操作系統或瀏覽器下的兼容,有些功能方面的測試,也要考慮到兼容性,包括操作系統兼容和應用軟件兼容,可能還包括硬件兼容。比如涉及到ajax、jquery、javascript等技術的,都要考慮到不同瀏覽器下的兼容性問題。
十、鏈接測試
主要是保證鏈接的可用性和正確性,它也是網站測試中比較重要的一個方面。
1、導航測試 導航描述了用戶在一個頁面內操作的方式,在不同的用戶接口控制之間,例如按鈕、對話框、列表和窗口等;或在不同的連接頁面之間。通過考慮下列問題,可以決定一個Web應用系統是否易于導航:導航是否直觀?Web系統的主要部分是否可通過主頁存取?Web系統是否需要站點地圖、搜索引擎或其他的導航幫助? 在一個頁面上放太多的信息往往起到與預期相反的效果。Web應用系統的用戶趨向于目的驅動,很快地掃描一個Web應用系統,看是否有滿足自己需要的信息,如果沒有,就會很快地離開。很少有用戶愿意花時間去熟悉Web應用系統的結構,因此,Web應用系統導航幫助要盡可能地準確。 導航的另一個重要方面是Web應用系統的頁面結構、導航、菜單、連接的風格是否一致。確保用戶憑直覺就知道Web應用系統里面是否還有內容,內容在什么地方。 Web應用系統的層次一旦決定,就要著手測試用戶導航功能,讓最終用戶參與這種測試,效果將更加明顯。2、圖形測試在Web應用系統中,適當的圖片和動畫既能起到廣告宣傳的作用,又能起到美化頁面的功能。一個Web應用系統的圖形可以包括圖片、動畫、邊框、顏色、字體、背景、按鈕等。圖形測試的內容有:(1)要確保圖形有明確的用途,圖片或動畫不要胡亂地堆在一起,以免浪費傳輸時間。Web應用系統的圖片尺寸要盡量地小,并且要能清楚地說明某件事情,一般都鏈接到某個具體的頁面。(2)驗證所有頁面字體的風格是否一致。(3)背景顏色應該與字體顏色和前景顏色相搭配。(4)圖片的大小和質量也是一個很重要的因素,一般采用JPG或GIF壓縮,最好能使圖片的大小減小到30k以下。(5)最后,需要驗證的是文字回繞是否正確。如果說明文字指向右邊的圖片,應該確保該圖片出現在右邊。不要因為使用圖片而使窗口和段落排列古怪或者出現孤行。通常來說,使用少許或盡量不使用背景是個不錯的選擇。如果您想用背景,那么最好使用單色的,和導航條一起放在頁面的左邊。另外,圖案和圖片可能會轉移用戶的注意力。
十一、業務流程測試(主要功能測試)
業務流程,一般會涉及到多個模塊的數據,所以在對業務流程測試時,首先要保證單個模塊功能的正確性,其次就要對各個模塊間傳遞的數據進行測試,這往往是容易出現問題的地方,測試時一定要設計不同的數據進行測試。
十二、安全性測試
(1)SQL注入(比如登陸頁面)
(2)XSS跨網站腳本攻擊:程序或數據庫沒有對一些特殊字符進行過濾或處理,導致用戶所輸入的一些破壞性的腳本語句能夠直接寫進數據庫中,瀏覽器會直接執行這些腳本語句,破壞網站的正常顯示,或網站用戶的信息被盜,構造腳本語句時,要保證腳本的完整性。
document.write("abc")
<script>alter("abc")</script>
(3)URL地址后面隨便輸入一些符號,并盡量是動態參數靠后
(4)驗證碼更新問題
(5)現在的Web應用系統基本采用先注冊,后登陸的方式。因此,必須測試有效和無效的用戶名和密碼,要注意到是否大小寫敏感,可以試多少次的限制,是否可以不登陸而直接瀏覽某個頁面等。
(6)Web應用系統是否有超時的限制,也就是說,用戶登陸后在一定時間內(例如15分鐘)沒有點擊任何頁面,是否需要重新登陸才能正常使用。
(7)為了保證Web應用系統的安全性,日志文件是至關重要的。需要測試相關信息是否寫進了日志文件、是否可追蹤。
(8)當使用了安全套接字時,還要測試加密是否正確,檢查信息的完整性。
(9)服務器端的腳本常常構成安全漏洞,這些漏洞又常常被黑客利用。所以,還要測試沒有經過授權,就不能在服務器端放置和編輯腳本的問題。
(10)用戶名密碼傳輸過程中是否加密傳輸。
十三、測試中應該注意的其他情況
1、在測試時,與網絡有關的步驟或者模塊必須考慮到斷網的情況
2、每個頁面都有相應的Title,不能為空,或者顯示“無標題頁”
3、在測試的時候要考慮到頁面出現滾動條時,滾動條上下滾動時,頁面是否正常
4、URL不區分大小寫,大小寫不敏感
5、、對于電子商務網站,當用戶并發購買數量大于庫存的數量時,系統如何處理
6、測試數據避免單純輸入“123”、“abc“之類的,讓測試數據盡量接近實際
7、進行測試時,盡量不要用超級管理員進行測試,用新建的用戶進行測試。測試人員盡量不要使用同一個用戶進行測試
8、提示信息:提示信息是否完整、正確、詳細
9、幫助信息:是否提供幫助信息,幫助信息的表現形式(頁面文字、提示信息、幫助文件),幫助信息是否正確、詳細
10、可擴展性:是否由升級的余地,是否保留了接口
11、穩定性:運行所需的軟硬件配置,占用資源情況,出現問題時的容錯性,對數據的保護
12、運行速度:運行的快慢,帶寬占用情況
感謝原作者
https://www.cnblogs.com/fighter007/p/8431133.html
*請認真填寫需求信息,我們會在24小時內與您取得聯系。