HTA版JSMin(省略修飾語若干)基于javascript語言編寫
久而久之,我發(fā)現(xiàn)這樣實(shí)在是太麻煩了!既然我們是程序員,為何不自己動(dòng)手把事情變得簡單一點(diǎn)呢?
因此我開始了對JSMin進(jìn)行“友好化”的工作。
而在進(jìn)行“友好化”工作的過程中,“不出意料”地遇到了一些意想不到的問題,馬上我就講遇到的是哪些問題、最后怎樣解決。
不過由于是在一切問題都解決之后才想起來寫文的,所以很抱歉,這次是“無圖無真相”。
在開始進(jìn)行講解之前,我先說明一下這次問題所涉及到的一些技術(shù)知識——如果你具有Windows腳本宿主的編程經(jīng)驗(yàn),那么這部分你可以跳過;如果你具有ASP的經(jīng)驗(yàn),那么這部分可能有一些內(nèi)容你已經(jīng)知道了。
Windows Script Host(WSH)即Windows腳本宿主,表現(xiàn)為一些可執(zhí)行文件(wscript.exe和cscript.exe),它們的功能是在不受Internet安全策略限制的情況下執(zhí)行用JScript或VBScript編寫的任務(wù)文件,從而進(jìn)行一些系統(tǒng)管理工作。值得注意的是,默認(rèn)情況下Windows中以.js為擴(kuò)展名的文件都被認(rèn)為是WSH文件,如果你嘗試雙擊一個(gè).js文件,你可能會(huì)發(fā)現(xiàn)一些錯(cuò)誤提示,或者被殺毒軟件攔截。
HTML Application簡寫為HTA,顧名思義,是“HTML應(yīng)用程序”——它和Windows腳本宿主一樣在相當(dāng)寬松的安全條件下執(zhí)行腳本,和Windows腳本宿主不同的地方是HTML應(yīng)用程序往往具有較好的圖形界面。本質(zhì)上說一個(gè)HTA文件其實(shí)就是使用了特殊擴(kuò)展名的HTM文件,如果你不想在制作界面上花費(fèi)太多的精力,而又正好同時(shí)具有HTML和WSH經(jīng)驗(yàn),那么使用HTML Application來解決問題是一個(gè)相當(dāng)不錯(cuò)的選擇。需要注意的是HTML Application也常被認(rèn)為是WPF技術(shù)的前身,如果你的目標(biāo)使用環(huán)境具有WPF支持(即.NET框架3.0+),那么使用WPF可能更加適合。HTA文件因?yàn)橐子趧?chuàng)作而一度被用于木馬下載器,時(shí)至今日可能仍有一些殺毒軟件粗暴地將HTA文件判斷為木馬下載器。就像.exe這樣的二進(jìn)制可執(zhí)行文件一樣,一個(gè)HTA文件是否有害是由自身設(shè)計(jì)決定的,而不是被文件類型先天決定。
FileSystemObject,簡稱FSO,是Windows腳本技術(shù)中為了方便腳本進(jìn)行文件系統(tǒng)操作而提供的組件對象,在ASP編程中也很常見。
WshShell,是WSH運(yùn)行環(huán)境為了方便腳本操作Shell相關(guān)物件而提供的組件對象,很多WSH程序都使用了這一對象。
ADODB.Stream,有的時(shí)候也被稱作“ADO Stream”,是ADO中為了方便操作二進(jìn)制數(shù)據(jù)流而提供的組件對象,但也經(jīng)常被用在數(shù)據(jù)庫以外的地方,例如文件操作。
“只要在文件圖標(biāo)上點(diǎn)點(diǎn)鼠標(biāo)就可以”這樣的功能特性被稱作“外殼關(guān)聯(lián)”之類的東西,很多時(shí)候大家對外殼關(guān)聯(lián)的認(rèn)知是“關(guān)聯(lián)到鼠標(biāo)雙擊”什么的。但稍微留心一點(diǎn)的人都會(huì)發(fā)現(xiàn)在點(diǎn)擊鼠標(biāo)右鍵彈出的快捷選單中,除了默認(rèn)的指令以外,還有一些其它的像是“編輯”、“打印”之類的指令。
而我們這一次要做的,就是為.js文件增加一個(gè)“Minimize”指令,當(dāng)我們點(diǎn)擊這個(gè)指令的時(shí)候就會(huì)啟動(dòng)JSMin給我們的ECMAScript代碼減肥。
就在這里,我遇到了第一個(gè)問題:
我編寫了一個(gè).js文件(實(shí)際上是一個(gè)WSH任務(wù)文件),用它來實(shí)現(xiàn)“自動(dòng)化”地“安裝”。
在Windows中為.js文件增添文件關(guān)聯(lián),需要在HKCR\.js這個(gè)注冊表項(xiàng)的“默認(rèn)”值所對應(yīng)的“JSFile”——HKCR\JSFile項(xiàng)之下的Shell項(xiàng)添加子項(xiàng),以及從屬的名為Command的子項(xiàng)。
在HKCR\JSFile\Shell下面添加了Minimize項(xiàng)并為Command子項(xiàng)設(shè)置了“我的hta文件路徑 "%1"”值以后,我發(fā)現(xiàn)使用這個(gè)Minimize指令后會(huì)產(chǎn)生一個(gè)“不是合法的可執(zhí)行文件”這樣的錯(cuò)誤,而如果在前面添加了start指令,又出現(xiàn)了“打開方式”對話框……
看起來在Shell項(xiàng)下面這么投機(jī)取巧好像是不行,所以我只好先讀取htafile的文件類型設(shè)置,然后將它設(shè)置到剛才新增加的Minimize指令中,.js文件中是這樣寫的:
view sourceprint?1 var asocCommand = wshShell.RegRead("HKEY_CLASSES_ROOT\\htafile\\Shell\\Open\\Command\\").replace("%1", instPath + "\\" + appExec).replace("%*", '"%1"');
外殼關(guān)聯(lián)的問題剛解決,緊接著就是命令行參數(shù)的解析問題:
最初我打算用WSH任務(wù)文件作為這個(gè)小工具的載體,但馬上我發(fā)現(xiàn)默認(rèn)情況下WSH文件缺乏UI支持——VBScript中的InputBox和HTML中的prompt在用JScript編寫的WSH任務(wù)文件中是不存在的,如果堅(jiān)持使用WSH就只能通過Windows控制臺來獲得用戶輸入。而如果通過控制臺來獲得用戶輸入的話,這個(gè)工具的標(biāo)準(zhǔn)使用流程就變成了“鼠標(biāo)點(diǎn)擊文件圖標(biāo)——快捷選單——Minimize——鍵盤輸入一個(gè)字符”,在一系列鼠標(biāo)操作(當(dāng)然,用鍵盤操作也是可能的)之后突然改用鍵盤,這好像不太對勁。
因此,我選擇了使用HTA這種可以提供豐富界面的文件類型來作為實(shí)現(xiàn)這個(gè)工具的途徑。
而選擇了HTA,也就意味著同樣要失去WSH“不外傳”的一些特性,例如缺少了用于解析命令行參數(shù)的“Arguments”對象。
而在我的構(gòu)思中,應(yīng)該是可以通過-level參數(shù)指定JSMin的代碼縮減等級、通過-silent參數(shù)來關(guān)閉提示信息的。如果不能從命令行中解讀出這些參數(shù),這些功能就沒有辦法實(shí)現(xiàn)。
因此我編寫了兩個(gè)函數(shù):
//========//========
// parse command-line info
// 在HTA環(huán)境中從命令行中解讀出被執(zhí)行的HTA的實(shí)際路徑和附加的參數(shù)
// 此部分代碼由NanaLich原創(chuàng),您可以不經(jīng)書面許可在任何情況下直接使用。您不應(yīng)擅自聲稱您或您的所屬機(jī)構(gòu)創(chuàng)作了這些代碼,也不應(yīng)該擅自以NanaLich的名義發(fā)布修改版本。
//========//========
function namedOrNot(args) {
var named = {}, not = [], c;
for(var i = 0; i < args.length; i++) {
c = args[i];
switch(c.charAt(0)) {
case "-":
case "/":
c = c.substring(1);
if(c.indexOf("=") > 0) {
c = c.split("=");
named[c.shift()] = c.join("=");
} else if(c.indexOf(":") > 0) {
c = c.split(":");
named[c.shift()] = c.join(":");
} else {
// 不能確定一個(gè)命名參數(shù)是不是也接受附加參數(shù),這是個(gè)未解難題
//i++;
named[c] = args[i + 1];
}
break;
default:
not.push(c);
break;
}
}
args.named = named;
args.unnamed = not;
}
function parseArgs(str) {
var a = [], q = false, c = "", $ = "";
function mit() {
if(c)
a.push(c);
}
for(var i = 0; i < str.length; i++) {
$ = str.charAt(i);
if($ == '"') {
q = !q;
} else {
if($ == " " && !q) {
mit();
c = "";
} else {
c += $;
}
}
}
mit();
namedOrNot(a);
return a;
}
//========//========
這樣,只要將HTA:Application對象的commandLine屬性傳入這個(gè)函數(shù),就可以獲得解析之后的命名和不命名參數(shù)了。
解決了命令行參數(shù)的問題之后,接下來就是文件的編碼問題。
在我們實(shí)際的Web開發(fā)過程中,我們可能因?yàn)楦鞣N各樣的原因使用“非Unicode區(qū)域編碼”和“Unicode(UTF-16)編碼”以外的其它編碼格式,例如“Unicode(UTF-16) Big Endian”和“UTF-8”這兩種編碼方式。
如果我們通過既有的文本編輯工具來復(fù)制、粘貼代碼,我們只要在保存文件的時(shí)候選擇編碼類型就可以了;但現(xiàn)在我們正在設(shè)計(jì)一種免去“打開——復(fù)制——粘貼——復(fù)制——粘貼——保存”這樣繁瑣的操作步驟的工具,我們就需要在這個(gè)工具中設(shè)計(jì)自動(dòng)適應(yīng)編碼類型的功能。
很遺憾,在我的測試中FSO和ADODB.Stream都不具備自動(dòng)識別文本編碼的能力,必須另想其它辦法——幸好,在HTA中VBScript也是默認(rèn)支持的,VBScript雖然沒有直接對字節(jié)組進(jìn)行操作的功能,但在VBScript中把字節(jié)組當(dāng)作字符串來進(jìn)行操作仍然可以在一定程度上滿足我們的要求。
因此,我編寫了下面這個(gè)函數(shù):
Function vbDetectFileEncoding(fn)
Dim Stream, B3
Set Stream = CreateObject("ADODB.Stream")
Stream.Type = 1
Call Stream.Open()
Call Stream.LoadFromFile(fn)
B3 = CStr(Stream.Read(3))
Call Stream.Close()
Set Stream = Nothing
Dim L1
L1 = Left(B3, 1)
If (L1 = ChrW(&hFEFF)) Then
vbDetectFileEncoding = "unicode"
Exit Function
Elseif (L1 = ChrW(&hFFFE)) Then
vbDetectFileEncoding = "unicodeFEFF"
Exit Function
Elseif B3 = (ChrB(&hEF) & ChrB(&hBB) & ChrB(&hBF)) Then
vbDetectFileEncoding = "utf-8"
Exit Function
End If
vbDetectFileEncoding = defEncoding
End Function
這個(gè)函數(shù)根據(jù)一個(gè)文本文件中可能存在的BOM來推斷文件所采用的編碼方式。
這里需要注意的一點(diǎn)是:
ADODB.Stream的相關(guān)文檔中寫道Allowed values are typical strings passed over the interface as Internet character set names (for example, "iso-8859-1", "Windows-1252", and so on). For a list of the character set names that are known by a system, see the subkeys of HKEY_CLASSES_ROOT\MIME\Database\Charset in the Windows Registry.,而注冊表中和“Unicode Big Endian”(Encoding 1201)相對應(yīng)的項(xiàng)目是“unicodeFFFE”,從這些信息上推斷在ADO Stream中使用“Unicode Big Endian”編碼時(shí)應(yīng)該指定Charset屬性為“unicodeFFFE”;
這里有一個(gè)容易混淆的實(shí)施是:BOM字符的Unicode編號是U+FEFF,而“一般的”“Unicode編碼”實(shí)為“Unicode Little Endian”——BOM字符會(huì)被寫成“FF FE”這樣兩個(gè)字節(jié),而在“Unicode Big Endian”中才會(huì)被寫成“FE FF”。
那么到這里,問題就出現(xiàn)了——如果和“unicodeFFFE”正相反的是“unicodeFEFF”的話,我們可以理解這里面的“FEFF”是BOM字符的Unicode編號;但與此同時(shí)代表“Unicode Big Endian”的“unicodeFFFE”中的“FFFE”具有什么含義呢?顯然這不可能是“一個(gè)Unicode編號為U+FFFE的字符”的意思。
而在實(shí)踐中,我發(fā)現(xiàn)無論為Charset屬性設(shè)置“unicode”還是“unicodeFFFE”,輸出的文件都是采用“Unicode (Little Endian)”編碼的,這顯然和文檔所表達(dá)的意思不符。
當(dāng)我再進(jìn)一步作出嘗試的時(shí)候,卻發(fā)現(xiàn)如果希望輸出“Unicode Big Endian”編碼的文件,Charset屬性應(yīng)該設(shè)置為“unicodeFEFF”——我們很容易發(fā)現(xiàn),“FEFF”正好符合BOM字符在文件中的字節(jié)順序;我們同樣可以發(fā)現(xiàn)為Charset屬性設(shè)置“unicodeFXFX”時(shí)其實(shí)就相當(dāng)于“用FXFX這樣的字節(jié)順序來進(jìn)行編碼”的意思,是享受特殊對待的,并不完全符合注冊表中所寫的樣子。
到現(xiàn)在為止,小工具已經(jīng)可以讀寫“Unicode”、“Unicode Big Endian”、“UTF-8”和“非Unicode區(qū)域編碼”這些編碼方式的文件了,只是我仍然沒想明白為什么文檔和注冊表中會(huì)寫著有誤或者完全無用的信息……
上面這些問題被一一解決以后,好像就再也沒有什么值得研究的問題了。
但是在使用JSMin的過程中,卻發(fā)現(xiàn)了另一個(gè)問題:
有些人(比方說我)主要使用Windows工作,Windows中文本文件的默認(rèn)行分隔符號是CR LF對。
而JSMin會(huì)把所有的CR都替換成LF,這樣的話CR LF對就變成了兩個(gè)LF的“LF LF”對了。
按照J(rèn)SMin本來的設(shè)計(jì),控制字符LF通常都是要被縮減掉的,因此兩個(gè)連續(xù)的LF也不是什么問題;但jsmin.js有一個(gè)新增的功能是保留具有一定特征(/*! ... */)的“重要注釋”,而如果在這種“重要注釋”中存在CR LF對的話,最終會(huì)變成無法去除的兩個(gè)LF控制字符,這可不好。
為了解決這個(gè)問題,我對jsmin.js稍微作了一些修改……不過因?yàn)镴SMin有點(diǎn)超出我的理解能力,我也只能把CR變成空格,不過好在這樣做在Windows中也有一些好處(在大部分的Windows版本中,單獨(dú)的LF控制字符在“記事本”程序中幾乎無法觀察到,用它分隔的兩行文本看起來也像是粘在了一起),就這么湊合用吧……這部分修改我就不單獨(dú)列出來了。
本文中所提到的一切代碼,都可以在這個(gè)壓縮包中找到。
壓縮包內(nèi)包含三個(gè)文件:install.js是注冊文件關(guān)聯(lián)的“安裝”腳本、jsmin.hta是使用“Minimize”指令時(shí)實(shí)際運(yùn)行的“應(yīng)用程序”、jsmin.js則是我修改以后的jsmin.js。
將三個(gè)文件解壓至同一個(gè)文件夾之后,雙擊install.js即可安裝本工具。如果您重新安裝了操作系統(tǒng),您可能會(huì)發(fā)現(xiàn)工具仍然遺留在您的個(gè)人文件夾中;只要您雙擊個(gè)人文件夾中遺留下來的install.js,您就又可以使用本工具了。
注意:自Windows XP起,較新版本的Windows會(huì)為從網(wǎng)絡(luò)上下載而來的文件設(shè)置一個(gè)標(biāo)志,這個(gè)標(biāo)志可能會(huì)讓HTA文件不能正常執(zhí)行,如果你在使用的時(shí)候碰到了這樣的問題,請點(diǎn)擊文件屬性對話框中的“解除鎖定”按鈕以去除這個(gè)標(biāo)志。
更新:修改了install.js?,F(xiàn)在在64位Windows 7上也可以正確安裝了;安裝以后也不用手動(dòng)“解除鎖定”了。
秘密在這里:
var appsPath = wshShell.ExpandEnvironmentStrings(wshShell.RegRead(regUSF + "Personal")) + "\\Scriptlet";
try{
fso.OpenTextFile(instPath + "\\" + appExec + ":Zone.Identifier", 1).Close();
fso.OpenTextFile(instPath + "\\" + appExec + ":Zone.Identifier", 2).Close();
}catch(ex){ }
文件打包下載 文件附一個(gè)改名的jse.方便經(jīng)常開發(fā)js的朋友,以免混淆。
因?yàn)楹枚嗯笥咽怯玫膚in2003開發(fā),.js文件使用普通文本打開的,不可能以后用js都讓運(yùn)行吧,直接改成install.jse即可運(yùn)行了,呵呵。
感謝作者發(fā)布這么好的東西。作者的blog地址
http://www.cnblogs.com/NanaLich
相關(guān)文章
js代碼延遲一定時(shí)間后執(zhí)行一個(gè)函數(shù)的實(shí)例
下面小編就為大家?guī)硪黄猨s代碼延遲一定時(shí)間后執(zhí)行一個(gè)函數(shù)的實(shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-02-02
layui點(diǎn)擊導(dǎo)航欄刷新tab頁的示例代碼
今天小編就為大家分享一篇layui點(diǎn)擊導(dǎo)航欄刷新tab頁的示例代碼,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08
用 javascript 實(shí)現(xiàn)的點(diǎn)擊復(fù)制代碼
用 javascript 實(shí)現(xiàn)的點(diǎn)擊復(fù)制代碼,需要的朋友可以參考一下2007-03-03
javascript通過元素id和name直接取得元素的方法
這篇文章主要介紹了javascript通過元素id和name直接取得元素的方法,涉及javascript獲取元素的相關(guān)技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-04-04
js將當(dāng)前時(shí)間格式化為 年-月-日 時(shí):分:秒的實(shí)現(xiàn)代碼
這篇文章主要介紹了js將當(dāng)前時(shí)間格式化為 年-月-日 時(shí):分:秒主要是使用js的Date()對象,將系統(tǒng)當(dāng)前時(shí)間格式化為年-月-日 時(shí):分:秒,需要的朋友可以參考下2018-01-01
Leaflet?數(shù)據(jù)可視化實(shí)現(xiàn)地圖下鉆示例詳解
這篇文章主要為大家介紹了Leaflet數(shù)據(jù)可視化實(shí)現(xiàn)地圖下鉆示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
JavaScript對象字面量和構(gòu)造函數(shù)原理與用法詳解
這篇文章主要介紹了JavaScript對象字面量和構(gòu)造函數(shù),結(jié)合實(shí)例形式分析了JavaScript對象字面量和構(gòu)造函數(shù)相關(guān)概念、原理、用法及操作注意事項(xiàng),需要的朋友可以參考下2020-04-04
js點(diǎn)擊button按鈕跳轉(zhuǎn)到另一個(gè)新頁面
點(diǎn)擊按鈕怎么跳轉(zhuǎn)到另外一個(gè)頁面呢?點(diǎn)擊圖片要跳轉(zhuǎn)到新的頁面時(shí),怎么做到呢?可以使用onclick=window.location=新頁面來實(shí)現(xiàn)2014-10-10
詳談js中數(shù)組(array)和對象(object)的區(qū)別
下面小編就為大家?guī)硪黄斦刯s中數(shù)組(array)和對象(object)的區(qū)別。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-02-02

