初在研究對移動網絡傳輸進行功耗優化,在一次意外的監聽網絡傳輸包中截獲了微信小程序的請求包,借此來窺探當下前端代碼安全。
0x01 小程序分析
小程序包結構
Segment | Name | Length | Remark |
---|---|---|---|
Header | FirstMark | 1 byte | 0xBE 固定值 |
Edition | 4 bytes | 0 -> 微信分發到客戶端 1 -> 開發者上傳到微信后臺 | |
IndexInfoLength | 4 bytes | 索引段的長度 | |
BodyInfoLength | 4 bytes | 數據段的長度 | |
LastMark | 1 byte | 0xED 固定值 | |
FileCount | 4 bytes | 文件數量 | |
Index | NameLength | 4 bytes | 文件名長度 |
Name | NameLength bytes | 文件名,長度為NameLength | |
FileOffset | 4 bytes | 文件在數據段位置 | |
FileSize | 4 bytes | 文件大小 | |
LOOP...... | |||
Data | Files Package...... |
包結構非常清晰,分為三個部分:
頭信息,包含一些包的標識,版本定義等,包含了三個冗余字段 --- 索引段和數據段的長度應該是用于做校驗,但實質上沒有用(設計者覺得需要設計一些冗余字段來確保設計的完整性,防止解析的時候溢出,但實際工程實踐中并沒有起到相應的作用),文件數量應該是用于簡化包解析過程,實際上知道了索引段長度或數據段長度中任何一個皆可推算出文件數量。
索引段,包含文件的元信息 --- 文件名以及文件位置(通過FileOffset和FileSize定位數據段中的文件)。如果從精簡包的大小的角度來看,FileOffset和FileSize只需一個存在即可,但是這樣解析包的難度就大大增加了,還是以工程實踐為主。
數據段,將所有的文件羅列在一起。
由此可見,數據完全沒有經過壓縮或者加密,連包的簽名信息也沒有。這導致只能在制品流程上進行嚴格控制,例如在開發者上傳代碼過程中需要授信,必須經過審查,也一定得由授信平臺進行代碼分發等。這些都無關風月,畢竟App Store就是這種模式,但是......
如何拆解這種自定義文件格式呢?
對多個相同格式的文件進行對比,對大體結構有宏觀的感覺,很容易發現一些固定的字段以及一些結構的長度。對于像小程序這種有軟件本體的例子,還可以通過微量修改來觀察文件的變化來找到文件結構和意義。
觀察特殊形式,首先英文的字符串是很明顯的,一般hex編輯器都自帶字符串化窗口,如果發現常見的字符串,就可以繼續去尋找字符串的邊界,字符串在二進制文件里有兩種儲存方式:一種是不記錄字符串的長度,讀取字符串到0x00位置,另一種一定在某一個地方儲存了這個字符串的長度,因此一旦得知了該字符串的內容,搜索該長度字段即可獲取更多的信息。其次一些文件頭也非常顯眼,例如PNG[1]、ZIP[2]等通用標準文件格式都有固定的文件頭,在小程序的自定義格式中很容易發現一些png、jpg等資源的文件頭,因此可以定位數據區的位置。
對特定區域的二進制進行推理猜測,一般來說二進制文件里需要儲存大量的offset和size的數據作為數據段的索引。offset相當于一個指針 - 索引文件在數據段的位置,工程實踐中,大部分儲存了offset的地方也會儲存size字段,畢竟在解析文件的時候會方便很多,也可以防止校驗數據出現指針越界。因此,一旦確認了文件中的數據段,就可以通過它的位置(offset)和大小(size)的實際數據進行搜索,逆向找到指向它的數據位置,并且繼續逆向直到解析完整的文件。另外,如果要考慮設計的完備性,需要在二進制文件中加入一些冗余字段進行校驗或者糾錯,例如CheckSum、CRC32、Alder32、MD5、ECC等,這些通過hex編輯器很容易計算并發現。小程序中FileCount的字段,這完全是為了工程實踐考慮的,在小程序中并沒有出現這類的計算值,這是可能是因為小程序為了簡單設計考慮,一旦發現包體被篡改或損壞就直接丟棄。
其實拆解小程序這種格式并不需要花費特別多的精力,因為其格式比較簡單,而且從下圖流程上來說,后綴為wx的二進制格式很可能與wxapkg格式是同源的。
開發者工具上傳服務器分發原始代碼后綴為wx代碼包處理為wxapkg格式包體客戶端
從開發者工具的代碼中的pack.js很容易發現一些對wx格式封裝的痕跡,只不過其中unpack.js的代碼被隱去了。通過實際的分析發現(wxapkg文件可以通過截獲網絡包請求獲得或者在本地的微信appbrand目錄下可以發現),wxapkg格式就是將wx格式進行了轉化:Wxml -> Html、 Wxml -> JS、Wxss -> Css,其二進制格式跟后綴名為wx二進制格式完全一致。我寫了兩個版本的解析二進制包的代碼(Javascript版本傳送門,python版本傳送門),其實非常簡單,根據小程序包結構一步一步解析,基本上沒啥難度。但如果要將Html -> Wxml, JS -> Wxml, Css -> Wxss進行還原,其中JS -> Wxml的過程中需要將if語句轉變成wx-if標簽、for語句轉化成wx-for標簽有點麻煩,需要對解析包后的page-frame.html中 JS 代碼進行修改,修改細節太多就不再詳說了,總之微信小程序的代碼沒有經過額外的保護措施,比較容易進行還原。
(PS:暴露一下微信小程序未公開的API,openUrl- 在小程序中打開外部網頁;getGroupInfo- 獲得群的名稱,群內成員昵稱等數據;getOpenidToken - 獲得用戶openid;這些權限微信應該是沒有準備開放的。每次在進入小程序時,客戶端都需要先去請求該小程序的元數據,例如應用名、版本號、一些權限列表、代碼包下載地址等描述信息,修改這些元數據可以獲得相應的權限,小程序的關鍵信息完全由后臺控制進行配置,另外小程序的本地文件存儲采用HASH映射機制進行文件定位,文件存儲在外部存儲,本身通過自定義算法實現完整性校驗 - 首先,小程序最終存儲的文件名是:對稱加密(文件流內容Alder32校驗和 | 原始文件名)生成的,最終文件名和文件內容會通過自校驗判斷完整性;其次,本地緩存是通過HASH映射查找文件。所以即使能破解文件名和文件內容,繞過文件自身簽名校驗,篡改為攻擊者的偽造文件,小程序APP也無法映射到該偽造文件進行使用。)
由上可見,微信并沒有在代碼安全上進行過多的考慮。這導致需要在應用審核過程中花費比較多的功夫,不然作品太容易被復制竄改,以至于會失去渠道先機,這對流量是致命打擊。由于歷史原因,前端的代碼安全技術發展的比較緩慢,相比其他被編譯成二進制的應用,前端這種純文本應用,太容易被辨識與竄改。
對前端代碼進行保護的目的在于讓機器容易識別相關的指令,而使人難以理解代碼的邏輯,但往往在對前端代碼進行保護過程中,很難既兼顧指令效率又能使可讀性降低。因此,常常需要在現有的代碼中增加一些額外的驗證邏輯,例如一些增加無效的代碼進行混淆、采用守護代碼保護業務代碼不能在其他的域名下正常運行、增加一些防止調試跟蹤的斷點等,這些措施都是使得破解代碼時人工成本增加,從而增加代碼的安全性。
下面提供一些能夠增加前端代碼安全性的策略:
1. 精簡(minify)
這是最簡單且無害的方法,精簡代碼能減少代碼體積,從而減小數據傳輸的負荷,同時也能降低代碼的可讀性。在小程序開發者工具中也提供該選項。對Javascript代碼進行精簡大致可以從以下幾個方面入手:
刪除注釋,刪除無意義或者多余的空白,刪除可以省略的符號
刪除一些沒有調用的代碼(Dead code),對函數進行精簡(三元運算符?:、字符串操作、對象函數、對象繼承、函數引用、無名函數、遞歸函數)
將變量名進行簡化,將零散的變量聲明合并,縮短語句
......
常用的工具有很多:YUI Compressor、UglifyJS、Google Closure Compiler、JS Packer、JS Min...
使用工具對代碼進行精簡時需要注意:1. 最好備份原始代碼,方便調試與后期修改。 2. 用于調試精簡代碼時保存的sourcemap,在線上應該刪除。 3.編寫代碼的時候應該嚴格按照規范,最好使用lint工具對代碼進行檢查,精簡代碼后導致代碼不可用時,調試非常困難。
這種簡單的方法雖然很實用,但是也很容易被還原出源代碼,使用一些代碼格式化工具可以補齊被刪除的空格、換行、符號等,例如jsbeautifier。另外2015年就有相關的研究,從大量的代碼中推測出被精簡的代碼,因為人寫代碼總有固定的范式,所寫的代碼相似性都非常的高,如果用統計方式就很容易反推源代碼,蘇黎世聯邦理工大學Martin Vechev教授領帶下開發的工具JSNice就是一款運用條件隨機場(Conditional Random Fields)機器學習和程序分析方法來還原Javascript代碼利器,利用大量的開源代碼,去學習命名和類型的規律。JSNice可以用于以下不同的方面:反精簡的JavaScript代碼、對當前的代碼提供更多的更有意義的變量名、自動化程序的注釋等。相關論文傳送門 后臺代碼傳送門
2. 混淆(obfuscation)
混淆可以減低代碼的可讀性,防止被輕易追蹤出程序邏輯。常見的混淆方法有如下幾種:
通過編碼混淆代碼,這篇文章《Javascript常用混淆方法》里面介紹了很多不錯的編碼加密方法。但是這些方法有個明顯缺點,增加代碼體積,而且編碼加密都是可逆的。
將標識符混淆和控制邏輯混淆(分離靜態資源、打亂控制流、增加無義的代碼等),例如aaencode和jjencode。
標識符混淆的方法有多種,有些與編碼混淆代碼方法有些重疊,常用方法有哈希函數命名、標識符交換和重載歸納等。哈希函數命名是簡單地將原來標識符的字符串替換成該字符串的哈希值,這樣標識符的字符串就與軟件代碼不相關了;標識符交換是指先收集軟件代碼中所有的標識符字符串,然后再隨機地分配給不同的標識符,該方法不易被攻擊者察覺;重載歸納是指利用高級編程語言命名規則中的一些特點,例如在不同的命名空間中變量名可以相同,使代碼中不同的標識符盡量使用相同的字符串,增加攻擊者對軟件源代碼的理解難度。
控制混淆是改變程序的執行流程,從而打斷逆向分析人員的跟蹤思路,達到保護軟件的目的。一般采用的技術有插入指令、偽裝條件語句、斷點等。偽裝條件語句是當程序順序執行從A到B,混淆后在A和B之間加入條件判斷,使A執行完后輸出TRUE或FALSE,但不論怎么輸出,B一定會執行。控制混淆采用比較多的還有模糊謂詞、內嵌外聯、打破順序等方法。模糊謂詞是利用消息不對稱的原理,在加入模糊謂詞時其值對混淆者是已知的,而對反混淆者卻很難推知。所以加入后將干擾反匯編者對值的分析。模糊謂詞的使用一般是插入一些死的或不相關的代碼(bogus code),或者是插入在循環或分支語句中,打斷程序執行流程。內嵌(in-line)是將一小段程序嵌入到被調用的每一個程序點,外聯(out-line)是將沒有任何邏輯聯系的一段代碼抽象成一段可被多次調用的程序。打破順序是指打破程序的局部相關性。由于程序員往往傾向于把相關代碼放在一起,通過打破順序改變程序空間結構,將加大破解者的思維跳躍[3]。
另外還有些混淆方式是專門針對于反混淆工具設計的,這就需要去仔細分析反混淆工具的原理,在一些特定的地方插入代碼使反混淆器進入死循環或者異常跳出。
一般來說,提供代碼精簡的工具都會提供一些混淆的方法,除此之外,比較知名的商業工具有jasob、jscrambler,一般越商業的越難被反混淆,然而這些高級的代碼混淆也常會被用于隱藏應用中的惡意代碼。對惡意代碼進行混淆是為了躲避殺毒軟件的檢測,這些代碼在被混淆擴充后會難以被識別為惡意軟件。相應的也有一些反混淆的工具出現,例如上面提到的JSNice工具能夠對混淆的代碼進行推理,另外反混淆工具JSDetox專門針對一些混淆方法做過專門的支持。反混淆一直是一項體力活,根據不同的混淆策略需要進行反推演算,這就是一場攻與防的游戲罷了。
3. 加密(encryption)
加密的關鍵思想在于將需要執行的代碼進行一次編碼,在執行的時候還原出瀏覽器可執行的合法的腳本,在某個角度也可以認為是一種混淆的形式,看上去和可執行文件的加殼有點類似。Javascript提供了將字符串當做代碼執行(evaluate)的能力,可以通過 Function constructor、eval、setTimeout、setInterval、Worker、DOM event等將字符串傳遞給JS引擎進行解析執行,由于有些需要用到eval函數,會導致代碼性能會減低。以Worker執行舉例:
var URL=window.URL || window.webkitURL;var Blob=window.Blob || window.webkitBlob;var blobURL=URL.createObjectURL( new Blob(['console.log("Hello World!")'], {type: 'application/javascript'}));new Worker(blobURL);URL.revokeObjectURL(blobURL);
有以下常見的幾種加密方法:
base64編碼,一種簡單的方法就是將代碼轉化成base64編碼,然后通過atob以及eval進行解碼然后運行,另外一種采用base62編碼技術更為常見,其最明顯的特征是生成的代碼以eval(function(p,a,c,k,e,r))開頭。無論代碼如何進行變形,其最終都要調用一次eval等函數。解密的方法不需要對其算法做任何分析,只需要簡單地找到這個最終的調用,改為console.log或者其他方式,將程序解碼后的結果按照字符串輸出即可。
(PS: 從算法上看,packer是一種base64編碼字典壓縮策略,packer的base64編碼的壓縮率很高,精簡后代碼依然可以減少50%體積以上,因為帶有解壓器和字符表,越長的代碼理論上壓縮率更高,想要了解詳情可以看看這篇文章《Packer,你對我的JS做了什么!》)
使用復雜化表達式,在Javascript中可以把原本簡單的字面量(Literal)、成員訪問(MemberExpression)、函數調用(CallExpression)等代碼片段變得難以閱讀。例如這個方法僅用+!等符號就足以實現幾乎任意Javascript代碼。在 JS 代碼中可以找到許多這樣互逆的運算,通過使用隨機生成的方式將其組合使用,可以把簡單的表達式無限復雜化。
隱寫術,將 JS 代碼隱藏到了特定的介質當中。如通過最低有效位(LSB)算法嵌入到圖片的 RGB 通道、隱藏在圖片 EXIF 元數據、隱藏在 HTML 空白字符、放到css文件中(利用content樣式能存放字符串的特性)等。比如一張圖片黑掉你:在圖片中嵌入惡意程序,這個正是使用了最低有效位平面算法,結合HTML5的canvas或者處理二進制數據的TypeArray,抽取出載體中隱藏的數據(如代碼)。隱寫的方式同樣需要解碼程序和動態執行,所以破解的方式和前者相同,在瀏覽器上下文中劫持替換關鍵函數調用的行為,改為文本輸出即可得到載體中隱藏的代碼[4]。
混合加密,單個方法容易被破解,但組合起來就不會那么容易了,破解成本也會指數增長,例如jdists采用組合加密和嵌套加密的方式。
這些加密的方式都很容易通過對源代碼進行詞法分析、語法分析進行還原,首先將代碼的字符串轉換為抽象語法樹(Abstract Syntax Tree, AST)的數據形式,然后從語法樹的根節點開始,使用深度優先遍歷整棵樹的所有節點,根據節點上分析出來的指令逐個執行,直到腳本結束返回結果。這種方法大多數用于對代碼進行優化,例如最近Facebook開源了代碼優化工具Prepack,可以自動消除冗余代碼,降低打包體積和執行時間,基本上就可以用來將這些加密的字符串進行還原,畢竟編碼這些字符串都是可以通過詞法語法推測出來的。
4. 編譯(compile)
Github上有一份清單記錄了所有Javascript擴展語言,這些語言都可以通過編譯器轉化為Javascript語言,這也是前端發展的一個趨勢,原來寫的html,css,Javascript已經開始變成了一個“中間語言”,而且越來越多的團隊也有了自己的一套前端編譯系統。Javascript越來越像Web中的匯編語言,特別是近些年Node的普及,讓前端變得越來越復雜,大量前端框架的出現,使得Javascript代碼可以通過手工編寫,也可以從另一種語言編譯而來,詳情參考幾年前Brendan Eich(JavaScript之父)、Douglas Crockford(JSON之父),還有Mike Shaver(Mozilla技術副總裁)的郵件。通過編譯后的Javascript代碼越方便機器的理解,降低可讀性,在某一定角度上講,這也不愧為一種代碼保護措施。據說幾大科技巨頭正在醞釀給瀏覽器應用設計一款通用的字節碼標準——WebAssembly,一旦這個設想得以實現,代碼保護將可以引入真正意義上的“加殼”或者虛擬機保護,對抗技術又將提升到一個新的臺階。目前在桌面端,使用NW.js框架可以JavaScript應用程序的源代碼可以被編譯為本地代碼,在運行時通過NW.js動態還原出源代碼,但是這種方法目前會比正常的JS代碼慢30%左右。
5. 防止被調試
對代碼進行破解分析無非分為靜態分析和動態分析,如果對代碼進行混淆加密等形式操作,那么靜態分析就很麻煩了,對代碼調試跟蹤分析可以對代碼整體邏輯有一個宏觀的把控。例如首先判斷瀏覽器是否開啟了開發者工具控制臺(目前最完美的解決方案傳送門),如果檢測出控制臺開啟則堵塞Javascript執行或讓代碼異常跳出。另外Android 4.4及以上和iOS是支持webkit remote debug的,因此應該在debug模式下,設置代碼可以被debug,release模式下,禁止debug以保護數據安全。
6. 前后端協作
首先得強調的事情是不要在前端放敏感數據,前端容易破解,因此需要配合后端進行安全防護,例如微信小程序的登錄,必須利用授信的后端配合才能完成此項功能,另外在小程序的網絡請求中的referer是不可以設置的,格式固定為https://servicewechat.com/{appid}/{version}/page-frame.html,其中{appid}為小程序的appid,通過驗證appid字段可以抵御一些直接的山寨,其次就是加快迭代速度更改代碼保護策略,這樣可以讓之前的分析失效,增加破解的成本。
以上就是對當前前端代碼安全進行的探索,最后用一句話結束:
Beneath this mask, there is more than flesh. Beneath this mask, there is an idea. And ideas are bulletproof.
作者:不詳
出處:知識商店
運行環境不同
網頁 —— 瀏覽器(內核渲染)
小程序 —— 微信環境API不同
由于運行環境不同,小程序無法調用DOM和BOM的API
但是小程序可以調用微信客戶端的API,如定位,掃碼支付等開發模式不同
網頁開發模式: 瀏覽器 + 代碼編輯器 (用記事本都可以敲出一個靜態頁面)
小城開發流程:1. 注冊開發賬號 2. 安裝小程序開發工具 3. 創建與配置小程序
相比較之下,小程序上手比較麻煩。
使用瀏覽器打開 https://mp.weixin.qq.com/ 網址,點擊右上角的“立即注冊”即可進入到小程序開發賬號進行注冊。
點擊注冊小程序 -> 填寫賬號信息 -> 填寫賬號信息 ->點擊鏈接激活賬號 ->選擇主體類型(這里選擇為個人即可) -> 主體信息登記 - > 重點: 獲取小程序自己的AppID,注冊后在開發設置即可找到
小程序開發工具 是微信所推薦的開發工具
她所提供有主要功能
快速創建小程序項目
對小程序項目代碼進行編寫
進行編譯和預覽
上傳代碼發布
推薦下載和安裝最新的穩定版(Stable Build)的微信開發者工具,下載頁面的鏈接如下:
https://developers.weixin.qq.com/miniprogram/dev/devtools/stable.html
效果:
pages 存放頁面的文件
utils 存放工具性質的模塊(腳本文件,如wxs腳本過濾文件)
app.js 小程序的入口文件
app.json 小程序的全局配置(配置窗口樣式版本,窗口路徑,tabBar導航條等)
app.wxss 小程序的全局樣式
project.config.json 小程序的項目配置
sitemap.json 設置小程序可否被搜索到
每一個頁面都有四個文件
.js 腳本文件(生命周期函數,存放數據,自定義函數)
.wxml 頁面結構文件 (編輯頁面UI結構)
.wxss 頁面樣式文件 (美化頁面樣式)
.json 頁面配置文件 (配置頁面,如當前窗口的外觀,引用自定義組件)
json 是一種數據格式,在實際開發中,json文件總是以配置文件存在,在小程序中也不例外
app.json 是關于項目的配置文件可以配置:
window:全局定義小程序所有窗口樣式(導航條,背景色,文字樣式等)
page:頁面路徑配置(創建頁面)
style:全局定義樣式版本設置
sitemaplocation: 用來指明 sitemap.json 的位置
只需要在 app.json -> pages 中新增頁面的存放路徑,小程序開發者工具即可幫我們自動創建對應的頁面文件,(放在第一位置路徑的頁面即為小程序主頁面 這里為 index頁面)
如圖所示:
這個是整個項目的配置文件,用來配置記錄我們對項目開發的個性化設置,如
setting:編譯配置(如:設置是否檢查sitemap)
appid:自己的appID
projectname: 項目名稱
微信現已開放小程序內搜索,效果類似于 PC 網頁的 SEO。
sitemap.json 文件用來配置小程序頁面是否允許能被搜索到
當開發者允許索引時,微信會以爬蟲的形式,為小程序的內容和項目名稱作為索引,用戶通過搜索關鍵字即可查到對應小程序
"action":"allow"
頁面默認是被允許索引的,要取消索引只需要設置為disallow
頁面的配置文件(局部配置)
可以用來配置當前頁面的窗口樣式,組件引用,與app.json的類似,
在page.json的配置項會覆蓋全局樣式app.json的配置項
WXML(WeiXin Markup Language)是小程序框架設計的一套標簽語言,用來構建小程序頁面的結構,其作用類似于網頁開發中的 HTML。
WXML 和 HTML 的區別
① 標簽名稱不同
HTML (div, span, img, a)WXML(view, text, image, navigator)
② 屬性節點不同
< a href=“#”>超鏈接< navigator url=“/pages/home/home”>
③ 提供了類似于 Vue 中的模板語法
數據綁定列表渲染條件渲染
WXSS (WeiXin Style Sheets)是一套樣式語言,用于描述 WXML 的組件樣式,類似于網頁開發中的 CSS(cascading style sheet)。
WXSS 和 CSS 的區別
① 新增了rpx單位
CSS 中需要手動進行像素單位換算,例如 remWXSS 在底層支持新的尺寸單位 rpx,在不同大小的屏幕上小程序會自動進行換算
② 提供了全局的樣式和局部樣式
項目根目錄中的 app.wxss 會作用于所有小程序頁面局部頁面的 .wxss 樣式僅對當前頁面生效
③ WXSS 僅支持部分 CSS 選擇器
.class 和 #idelement并集選擇器、后代選擇器::after 和 ::before 等偽類選擇器
在小程序中,我們通過 .js 文件來處理用戶的操作。例如:響應用戶的
點擊、獲取用戶的位置等等
小程序中的 JS 文件分為三大類(其他:自定義組件componnet),分別是:
① app.js
是整個小程序項目的入口文件,通過調用 App() 函數來啟動整個小程序
② 頁面的 .js 文件
是頁面的入口文件,通過調用 Page() 函數來創建并運行頁面
③ 普通的 .js 文件
是普通的功能模塊文件,用來封裝公共的函數或屬性供頁面使用
js(java script) 是一個實現業務邏輯的文件。
例如:Andriod安卓系統 和 IOS蘋果系統,是兩個不同的宿主環境,
安卓的應用必須要在安卓系統才能運行,這也是為什么 之前有些軟件 安卓和蘋果不能兼容了。
而小程序的宿主環境則是微信,小程序只能在微信上運行,小程序借助宿主環境提供的能力,可以完成許多普通網頁無法完成的功能,例如:微信掃碼、微信支付、微信登錄、地理定位、etc
小程序的宿主環境微信所包含的內容
通信模式運行機制組件API
小程序中通信的主體是渲染層和邏輯層,其中:
① WXML 模板和 WXSS 樣式工作在渲染層
② JS 腳本工作在邏輯層
小程序中的通信模型分為兩部分:
① 渲染層和邏輯層之間的通信
由微信客戶端進行轉發
② 邏輯層和第三方服務器之間的通信
由微信客戶端進行轉發
微信客戶端將代碼包下載到本地
解析app.json 全局配置文件
執行小程序入口文件app.js,即調用app.js 的App實例(相當于一個類)
渲染小程序首頁
解析page.json局部配置文件
執行頁面入口文件page.js,即調用page.js 的page()創建頁面實例
加載.wxml和.wxss 結構和樣式文件
恭喜你!!成功揚起小程序的揚帆!!!
信小程序初步入坑小指南
https://developers.weixin.qq.com/miniprogram/dev/devtools/beta.html
打開鏈接下載小程序云開發
為json格式的文件,為一個配置文件,屬于全局的
初始化的文件內容
{
"pages":[
"pages/index/index",
"pages/logs/logs"
],
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "WeChat",
"navigationBarTextStyle":"black"
}
}
pages
各個參數的解釋,pages為頁面,每一次更改頁面,增加或者新增加頁面都需要修改pages參數。不需要加后綴名,微信框架會自動添加后綴名。
window
對于全局導航欄的設置。
navigationBarBackgroundColor 設置全局的導航欄的顏色
navigationBarTitleText 設置導航欄的文字內容
navigationStyle 設置導航欄的樣式
backgroundColor 設置窗體的顏色,即下拉刷新透露出的顏色
即需要設置 "enablePullDownRefresh": true, 其布爾值為true即可進行漏出設置的窗體顏色。
backgroundTextStyle 設置下拉的loding樣式
tabBar
是下方的導航欄的設置。這個直接看文檔吧。。
https://developers.weixin.qq.com/miniprogram/dev/framework/config.html#%E5%85%A8%E5%B1%80%E9%85%8D%E7%BD%AE
app.json文件如下
{
"pages":[
"pages/index/index",
"pages/logs/logs"
],
"window":{
"backgroundTextStyle":"dark",
"navigationBarBackgroundColor": "#c7dbc8",
"navigationBarTitleText": "小小",
"navigationBarTextStyle":"whiter",
"enablePullDownRefresh": true,
"backgroundColor": "#fceaea"
},
"tabBar": {
"list": [{
"text": "ming",
"pagePath": "pages/logs/logs"
}, {
"text":"home",
"pagePath": "pages/index/index"
}],
"color": "#f8fcea",
"backgroundColor": "#ff9999",
"selectedColor": "#c5ff99",
"borderStyle": "white",
"position": "top"
},
"functionalPages": true
}
該文件為一個本地的配置文件
就是對于微信的一些設置
wxml ≈ html
感覺很像ejs,好吧
wxml中內容
<text>{{mesg}}</text>
在同路下的js文件中
Page({
data: {
msg: "hello world"
}
})
渲染出來的結果
Page({
data: {
msg: "hello world"
},
clickMe: function() {
this.setData({msg: "3333"});
}
})
wxml
<text>{{msg}}</text>
<button bindtap="clickMe">點擊按鈕</button>
點擊按鈕將會自動更新頁面的數據
客戶端打開小程序之前,會把小程序全部下載到本地。
通過app.json可以知道頁面的啟動地址。
小程序只有一個app的實例,全局共享。
啟動完成后觸發onLaunch事件,然后運行回調函數
在小程序啟動完畢以后控制臺輸出內容
App({
onLaunch: ()=> {
console.log('小程序啟動完畢')
}
})
上方進行了一次回調
一個頁面有四個文件,分別是json(配置文件),wxml(頁面文件,類似于html),js文件(處理頁面的相關交互,和網頁類似)
js中有一個page,為一個頁面的構造器,渲染頁面的時候先裝載json文件,配置當前的頂部導航,接著裝載wxml文件,配置頁面的DOM,在裝載wxss,進行對頁面樣式的處理
和網頁的類似,都是同樣的
最后將會讀取js文件,根據頁面中的page函數即構造器中的內容,將wxml和data進行綁定,渲染出結果,為mvvm
mvc 分別是模型層,視圖層,和控制器,當用戶請求到達以后,將會先經過路由,即入口文件,即主文件中的server.js文件,接著進入lib目錄下的route.js文件,對路由進行分發,路由在將數據傳遞給控制器,controller ,controller 收到請求以后再向model 索要數據,索要完成以后,在將數據導向view層,即ejs文件的地方,渲染完成文件以后返回給用戶。 mvp 在mvc的基礎上,view中不寫邏輯,,在原先控制器的地方完成頁面的合并
mvvm 和mvp類似,只不過view和原先的控制器雙向綁定,即使用get 和 set方式,達到當數據更改的時候,進行回調
是滴,小程序采用組件化,例如生成地圖map即可
ps 在網頁中,生成地圖,需要引入第三方的js文件,以及第三方的api,達到生成地圖的目的。
api的回調為異步操作,所以呢,依舊要進行回調
發布者-訂閱模型
小程序使用的是js引擎進行渲染,邏輯層將數據發送給視圖層,視圖層接受事件的反饋,開發者寫的所有文件都會打包成為一份js文件,小程序運行時啟動,小程序離開時銷毀,
吐槽 一些瀏覽器里的js在微信小程序無法使用,小程序還有npm? 天哪,
注冊程序
app()函數,必須在app.js文件中調用,接受一個object的參數
前臺后臺定義,當用戶點擊左上角關閉的時候, 或者按住home離開微信,小程序,沒有銷毀,將會進入后臺,再次打開進入前臺,當小程序進入后臺一段時間以后,系統資源占用過高將會不定時的銷毀
onLaunch
代碼如下
//app.js
App({
onLaunch: (onlaunch)=> {
console.log(onlaunch)
}
})
效果如下
即獲取頁面的參數
相比較網頁好輕松,,網頁還需要進行先字符串分隔,然后再次以=進行分隔,然后將其循環,遍歷該數組,將其保存進入對象,完成。如果使用json字符串進行傳,可能稍微方便一點
getAPP
getApp函數能獲取小程序的各種函數,即onLaunch等其他的一些函數
即獲取到小程序的一個實例
注冊頁面
page為一個構造函數,接受對象,用來對頁面進行初始化
data
data和渲染層,進行數據的綁定
onLoad
進行參數的傳值
Page({
data: {
msg: "hello world"
},
clickMe: function() {
this.setData({msg: "3333"});
},
onLoad: (query)=> {
console.log(query);
}
})
onShow
頁面切入的時候,將會進行顯示。
例: 按住home按鍵,在回到小程序界面的時候,將會回調該注冊的函數
onReady
頁面渲染完成以后,將會回調該函數
onHide
頁面切換的時候,將會回調注冊的函數
onUnload
頁面卸載的時候,將會觸發
頁面事件
onPullDownRefresh
用戶下拉,需要設置onReachBottomDistance,即用戶下拉到頁面底部多少的時候,觸發該事件
onPageScroll
用戶滑動的距離,沒有正負之分
onShareAppMessage
用戶轉發的接口
在button組件中設置
open-type="share"
即可設置為轉發按鈕
需要有return進行返回參數
onTabItemTap
單擊tab將會觸發該內容
onTabItemTap: (item)=>{
console.log(item.index)
console.log(item.pagePath)
console.log(item.text)
}
如果按住導航,將會進行輸出
當單擊組件的時候,發生事件
這一點終于和網頁類似了。網頁中也可以實現一個元素和事件進行相互的綁定
viewTap: ()=> {
console.log('您已經單擊按鈕')
}
<button bindtap="viewTap">這是按鈕</button>
Page.route
當前頁面的路徑,類似于網頁的
window.location.href
可以獲取到當前頁面的url
onShow: function() {
console.log('頁面進行顯示切入前臺');
console.log(this.route);
},
當用戶切換tab的時候,將會立馬輸出當前頁面的path值
其中this指代當前的page,因為是在一個page函數內部
Page.prototype.setData
為page的繼承函數,將數據從邏輯層發送到視圖層(異步),this.data的值,(同步
)
ps 單純的改變this.data的值,不會起作用,因為頁面已經渲染完成,需要進行發送到視圖層,進行更新視圖
ps 是的。小程序使用的就是mvvm
js
Page({
data: {
text0: "11111",
text1: "22222",
text2: "333333",
text3: "444444"
},
changeText0: function() {
this.setData({text0: "22222"})
},
changeText1: function () {
this.setData({ text1: "33333" })
},
changeText2: function () {
this.setData({ text2: "44444" })
},
changeText3: function () {
this.setData({ text3: "55555" })
},
})
wxml
<view>{{text0}}</view>
<button bindtap="changeText0">更改上方文字</button>
<view>{{text1}}</view>
<button bindtap="changeText1">更改上方文字</button>
<view>{{text2}}</view>
<button bindtap="changeText2">更改上方文字</button>
<view>{{text3}}</view>
<button bindtap="changeText3">更改上方文字</button>
路由
小程序中的路由是有框架達到的
框架用棧的方式維護了當前的所有頁面
ps 又見到棧了
getCurrentPages
該函數用于獲取當前頁面的棧,返回的是一個數組
適用于獲取上一個返回的頁面
全局變量
js文件中聲明的變量,和函數只在文件中有用,不同文件可以聲明相同的
ps 如果加載到一個頁面的時候,將會發生命名沖突
可以在app.js文件中設置全局的數據
// a.js
var app=getApp();
console.log(app.globalData)
//app.js
App({
onLaunch: (onlaunch)=> {
console.log(onlaunch)
},
globalData: 333
})
模塊化
類似于node.js的包
使用的同樣是exports進行接口的暴露
是滴,類比node.js即可,是滴,小程序還不支持引入npm包
然后就可以歡快的引入npm包了。
ps 貌似有個小坑。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。