我這裏說的前端加密,是對瀏覽器端的js文件加密,不是傳輸過程當中的加密,不涉及hash摘要等javascript
很好理解,就是去掉註釋、多於的空格、簡化標識符等等。工具不少,YUI Compressor、UglifyJS、Google Closure Compiler等等。css
保證不破壞代碼執行結果的狀況下,讓代碼變得難以閱讀。經常使用混淆規則:拆分字符串、拆分數組、增長廢代碼、,壓縮其實也有必定混淆功能。本質就是改變輸入代碼字符串的抽象語法樹(AST)的結構。其餘工具:v8就是一個,還有mozilla的SpiderMonkey, 知名的esprima,還有uglify;商業混淆服務有:jscramble。前端
這裏的加密指文本可逆編碼,是狹義的加密,也就是咱們常說的加密啦。這個部分依然是藉助一些工具,如: Packer 、bcrypt等等。java
將代碼放在非js文件中,增長定位難度。這裏經常使用的方式有兩種:放置到png中,經過HTML Canvas 2D Context獲取二進制數據的特性,能夠用圖片來存儲腳本資源;放置到css文件中,利用content樣式能夠存放字符串的特性,一樣能夠。程序員
用png保存js代碼,首先須要對png進行編碼,而後使用的時候進行解碼。藉助canvas及base64和二進制編碼。npm
一、字符串轉換成ascii碼;
二、建立足夠存儲空間的canvas;
三、將字符填入到像素中(忽略alpha值);
四、獲取data url;
canvas.toDataURL(「image/png」);
五、存爲png圖片。gulp
function encodeUTF8(str) { return String(str).replace( /[\u0080-\u07ff]/g, function(c) { let cc = c.charCodeAt(0); return String.fromCharCode(0xc0 | cc >> 6, 0x80 | cc & 0x3f); } ).replace( /[\u0800-\uffff]/g, function(c) { let cc = c.charCodeAt(0); return String.fromCharCode(0xe0 | cc >> 12, 0x80 | cc >> 6 & 0x3f, 0x80 | cc & 0x3f); } ); } function request(url, loaded) { let xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState == 4) if (xmlhttp.status == 200) loaded(xmlhttp); } xmlhttp.open("GET", url, true); xmlhttp.send(); } void function(){ let source = '../image/test.js'; request(source, function(xmlhttp){ let text = encodeUTF8(xmlhttp.responseText); let pixel = Math.ceil((text.length + 2) / 3); // 1一個像素存3個字節, let size = Math.ceil(Math.sqrt(pixel)); //console.log([text.length, pixel, size, size * size * 3]); let canvas = document.createElement('canvas'); canvas.width = canvas.height = size; let context = canvas.getContext("2d"), imageData = context.getImageData(0, 0, canvas.width, canvas.height), pixels = imageData.data; for(let i = 0, j = 0, l = pixels.length; i < l; i++){ if (i % 4 == 3) { // alpha會影響png還原 pixels[i] = 255; continue; } let code = text.charCodeAt(j++); if (isNaN(code)) break; pixels[i] = code; } context.putImageData(imageData, 0, 0); document.getElementById('base64').src = canvas.toDataURL("image/png"); }); }();
編碼後的圖片:
canvas
一、加載png;
二、將png原尺寸繪製到canvas中;
三、讀取像素中的字符串;
四、生成相應協議的data url使用。數組
void function(){ let source = '../image/test.png'; let img = document.createElement('img'); img.onload = function(){ let canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; let context = canvas.getContext("2d"); context.drawImage(img, 0, 0); let imageData = context.getImageData(0, 0, canvas.width, canvas.height), pixels = imageData.data; let script = document.createElement('script'); let buffer = []; for (let i = 0, l = pixels.length; i < l; i++) { if (i % 4 == 3) continue; // alpha會影響png還原 if (!pixels[i]) break; buffer.push(String.fromCharCode(pixels[i])); } script.src = 'data:text/javascript;charset=utf-8,' + encodeURIComponent(buffer.join('')); document.body.appendChild(script); script.onload = function(){ console.log('script is loaded!'); } img = null; } img.src = source; }();
這裏須要手動下載編碼後的圖片,我沒有寫自動下載的函數,這又是另外一個能夠深刻探討的問題了,因此不過多擴展。瀏覽器
使用content就簡單多啦。
let div = document.getElementById('content'); let content = window.getComputedStyle(div, ':before').content;
只須要和上面代碼同樣,新建一個srcript標籤,利用data協議,就能夠執行content內保存的js代碼啦。
eval = function() { console.log('eval', JSON.stringify(arguments)); }; eval('console.log("Hello world!")'); Function = function() { console.log('Function', JSON.stringify(arguments)); return function() {}; }; new Function('console.log("Hello world!")')();
可是可能不是全局使用:
(function(){}).constructor('console.log("Hello world!")')()
Function.prototype.__defineGetter__('constructor', function () { return function () { console.log('constructor', JSON.stringify(arguments)); }; }); (function() {}).constructor('console.log("Hello world!")');
目前能想到的是判斷 eval 是否被重定向
示例,若是 eval 被重定向 z 變量不會被泄露
(function(x){ var z = 'console.log("Hello world!")'; eval('function x(){eval(z)}'); x(); })(function() { /* ... */ });
uglify介紹
概述:
案例:Cesium打包流程,相關技術點和大概流程
原理:代碼優化的意義:壓縮 優化 混淆
優化:如何完善Cesium打包流程
關鍵字:Cesium gulp uglifyjs
字數:2330 | 閱讀時間:7min+
若是沒有記錯,Cesium從2016年初對代碼構建工具作了一次調整,從grunt改成gulp。做爲一名業餘選手,就不揣測二者的差異了。我的而言,gulp和Ant的思路很類似,經過管道鏈接,都是基於流的構建風格,並且gulp更像是JS的編碼風格,自帶一種親切感。
gulp.task('task1',['task0'], function() {
return fun_task1();
});
Task語句是gulp中最多見的,懂了這句話,就等於你看懂腳本了。這句話的意思是,要執行task1,須要先執行task0,而task1的具體工做都在fun_task1方法中。這就是以前說的基於流的構建風格。有了這句話,在命令行中鍵入:gulp task1,回車執行該指令便可。
先安裝Node,環境變量等,並安裝npm包後,便可使用gulp打包工具,這裏推薦cnpm。環境搭建好後,命令行中鍵入gulp minify開始打包。完整的過程是build->generateStubs->minify。
Cesium打包流程
build:準備工做,建立Build文件夾;將glsl文件轉爲js形式;最主要的是createCesiumJs方法,遍歷Source中全部js腳本,將全部Object記錄到Source/Cesium.js;其餘的是範例,單元測試相關模塊。
generateStubs:用於單元測試,略。
minify; 首先combineJavaScript主要作了兩件事情,打包Cesium和Workers腳本,這是打包的最終結果。Gulp根據指令的不一樣,好比minify下采用uglify2優化,而combine對應的參數爲none,生成路徑爲CesiumUnminified。
另外,細心的人會發現,combineCesium的實現中有這樣一句話path.relative('Source',require.resolve('almond')),這是一個小優化,almond是requirejs的精簡包,所以,最終的Cesium.js中包含'almond腳本,內置了requirejs的主要方法。
如上是Cesium打包的主要流程,簡單說主要有3+1類個指令:
Clean
清空文件
minify
打包&壓縮
combine
只打包,不壓縮
JScoverage
單元測試覆蓋率,不瞭解
2 代碼優化
對流程有了一個大概瞭解,下面,咱們詳細瞭解一下uglify2過程都作了哪些代碼優化,一言以蔽之,壓縮,優化,混淆。
uglify2主要有三個參數:-o,-c,-m,-o參數必選,指定輸出文件,-c壓縮,-m混淆變量名。以下分別爲combine、(uglifyjs -o)、(uglifyjs –c -m -o)的文件對比,單位是k:
uglify2的壓縮對比
都在一個屋檐下,差距怎麼就這麼大呢?咱們簡單說一下從1~2,2~3之間青取之於藍而勝於藍的過程。
1~2的過程其實很簡單,就是幹了三件事,去掉註釋, 去掉多餘的空格(換行符),去掉沒必要要的分號(;)。就這三件事情,文件一會兒小了一半多,換句話就是平時你寫的代碼有一大半都是廢話,此時你旁邊的AI程序員可能會喃喃道來「大家人類好愚蠢~」。
2~3則是不少小細節的綜合應用:
去掉一些實際沒有調用的函數(Dead code);
將零散的變量聲明合併,好比 var a; var b;變爲var a,b;
邏輯函數的精簡,好比if(a) b(); else c()變爲a ? b() : c();
變量名的簡化,好比var strObject;變爲var s;
……
這些小技巧有不少,具體要看不一樣的壓縮工具的考慮優劣,但有些壓縮高效的工具並不穩定,可能會破壞語法規範或語意,因此不必爲了幾個kb承擔過多的風險,目前比較成熟的工具主要有三個uglify2,google closure以及yuicompressor,具體優劣得本身來體會了,我是按照本身的理解給出的前後順序。最終的效果以下:
Cesium腳本效果
這樣的代碼只能用單位「坨」來形容了,人類是沒法直接讀懂的,那瀏覽器能讀懂嗎?這是一個好問題!以下是V8引擎對JS語法解析的大概流程:
V8引擎解析JS腳本
下面是在我本機Chrome解析Cesium.js腳本花費時間(腳本從下載完到瀏覽器解析完的時間差),單位毫秒,由於只測試了一次,可能會有偏差,但基本吻合指望值:
JS腳本解析時間對比
首先由於是本機測試,腳本不管是最大的8M仍是最小的2.4M,下載速度都很快,所以咱們不討論(但實際應用中要考慮)腳本下載所需時間。
其次,如上圖,多了一個source,這是源碼狀況下,這個時間水分比較大,由於是零散的文件,能夠作到按需下載,但由於文件比較瑣碎,性能也不高。
結論是,這種JS腳本優化策略對瀏覽器的影響不大,瀏覽器看到優化後的代碼,可能會愣一會神,但很快就克服了。
3實戰
知道了代碼優化的大概原理,回顧一下代碼優化的目的(壓縮,優化,混淆),匹配一下結果是否符合指望值。嗯,其一,腳本的大小小了,其二,代碼效率也優化了,其三,別人也看不懂了。彷佛該作的都已經作了,這個腳本已經很完美了。
Format後的效果
毛爺爺說,與人鬥其樂無窮。確實,前兩點的目的達到了,但第三點,還差不少。如上,和剛纔的腳本是同一個文件,我只是用Chrome的調試工具format而已。這就是理想和現實之間的差距。
可見,Cesium默認打包工具在壓縮和優化上都沒有問題,但在混淆上並不充分,固然Cesium自己是開源的,也不必搞這些。客觀說,JS腳本是明碼的,因此反編譯只是時間和能力的問題,因此不妨換個態度來看待這個問題,增長反編譯的成本,當該成本大於購買成本便可