看如下代码示例,一段内联CSS,和一段内联JavaScript,但真正的核心代码却隐藏在CSS文件中。这里用到了CSS的content属性,content属性通常与:before及:after伪元素配合使用,来插入生成内容。这里我们创建了一个隐藏的元素,id为#jhtmls,其content内容是我们base64编码过之后的JavaScript代码:
<!doctype html> <html> <head> <meta charset="utf-8"> <style type="text/css"> #style-code #jhtmls { display: none; visibility: hidden; content: 'dm9pZCBmdW5jdGlvbihlKXsidXNlIHN0cmljdCI7ZnVuY3Rpb24gbihlKXtyZXR1cm4gU3RyaW5nKGUpLnJlcGxhY2UoL1siPD4mIF0vZyxmdW5jdGlvbihlKXtyZXR1cm4iJiIrb1tlXSsiOyJ9KX1mdW5jdGlvbiB0KGUpe3ZhciBuPVtdO3JldHVybiBuLnB1c2goIndpdGgodGhpcyl7Iiksbi5wdXNoKGUucmVwbGFjZSgvPChzY3JpcHR8c3R5bGUpW14+XSo+W1xzXFNdKj88XC9cMT4vZyxmdW5jdGlvbihlKXtyZXR1cm5bJyEje3VuZXNjYXBlKCInLGVzY2FwZShlKSwnIil9J10uam9pbigiIil9KS5yZXBsYWNlKC9bXHJcbl0rL2csIlxuIikucmVwbGFjZSgvXlxuK3xccyskL2dtLCIiKS5yZXBsYWNlKC9eKFsgXHdcdF8kXSooW14mXF4/fFxuXHdcLycie31cW1xdK1wtKCk6OyBcdD1cLiRfXXw6XC9cLykuKiR8Xig/IVxzKihlbHNlfGRvfHRyeXxmaW5hbGx5fHZvaWR8dHlwZW9mXHNbXHckX10qKVxzKiQpW14nIjo7e30oKVxufD0mXC9eP10rJClccz8vZ20sZnVuY3Rpb24oZSl7cmV0dXJuIGU9ZS5yZXBsYWNlKC8mbm9uZTsvZywiIikucmVwbGFjZSgvWyInXFxdL2csIlxcJCYiKS5yZXBsYWNlKC9cbi9nLCJcXG4iKS5yZXBsYWNlKC8oIT8jKVx7KC4qPylcfXwoIT9cJCkoW2Etel9dK1x3Kig/OlwuW2Etel9dK1x3KikqKS9nLGZ1bmN0aW9uKGUsbix0LHIsdSl7aWYociYmKG49cix0PXUpLCF0KXJldHVybiIiO3Q9dC5yZXBsYWNlKC9cXG4vZywiXG4iKS5yZXBsYWNlKC9cXChbXFwnIl0pL2csIiQxIik7dmFyIG89L15bYS16JF1bXHcrJF0rJC9pLnRlc3QodCkmJiEvXih0cnVlfGZhbHNlfE5hTnxudWxsfHRoaXMpJC8udGVzdCh0KTtyZXR1cm5bIicsIixvP1sidHlwZW9mICIsdCwiPT09J3VuZGVmaW5lZCc/Jyc6Il0uam9pbigiIik6IiIsIiMiPT09bnx8IiQiPT09bj8iX2VuY29kZV8iOiIiLCIoIix0LCIpLCciXS5qb2luKCIiKX0pLGU9WyInIixlLCInIl0uam9pbigiIikucmVwbGFjZSgvXicnLHwsJyckL2csIiIpLGU/WyJfb3V0cHV0Xy5wdXNoKCIsZSwiKTsiXS5qb2luKCIiKToiIn0pKSxuLnB1c2goIn0iKSxuZXcgRnVuY3Rpb24oIl9vdXRwdXRfIiwiX2VuY29kZV8iLCJoZWxwZXIiLCJqaHRtbHMiLG4uam9pbigiIikpfWZ1bmN0aW9uIHIoZSxyLG8peyJmdW5jdGlvbiI9PXR5cGVvZiBlJiYoZT1TdHJpbmcoZSkucmVwbGFjZSgvXlteXHtdKlx7XHMqXC9cKiE/WyBcZlx0XHZdKlxuP3xbIFxmXHRcdl0qXCpcL1s7fFxzXSpcfSQvZywiIikpO3ZhciBpPXQoZSksYz1mdW5jdGlvbihlLHQpe3ZhciByPVtdO3JldHVybiJ1bmRlZmluZWQiPT10eXBlb2YgdCYmKHQ9ZnVuY3Rpb24oZSl7aS5jYWxsKGUscixuLHQsdSl9KSxpLmNhbGwoZSxyLG4sdCx1KSxyLmpvaW4oIiIpfTtyZXR1cm4gYXJndW1lbnRzLmxlbmd0aDw9MT9jOmMocixvKX12YXIgdT11fHx7fSxvPXsnIic6InF1b3QiLCI8IjoibHQiLCI+IjoiZ3QiLCImIjoiYW1wIiwiICI6Im5ic3AifTt1LnJlbmRlcj1yLCJmdW5jdGlvbiI9PXR5cGVvZiBkZWZpbmU/KGRlZmluZS5hbWR8fGRlZmluZS5jbWQpJiZkZWZpbmUoZnVuY3Rpb24oKXtyZXR1cm4gdX0pOiJ1bmRlZmluZWQiIT10eXBlb2YgbW9kdWxlJiZtb2R1bGUuZXhwb3J0cz9tb2R1bGUuZXhwb3J0cz11OndpbmRvd1tlXT11fSgiamh0bWxzIik7'; } </style> </head> <body> <div></div> <script> (function() { var map = document.createElement('map'); map.setAttribute('id', 'style-code'); map.innerHTML = '<area id="jhtmls"></area>'; document.body.appendChild(map); String(getComputedStyle(map.querySelector('#jhtmls'), false).content).replace(/[\w+=\/]+/, function(all) { eval(atob(all)); }); document.body.removeChild(map); })(); (function() { document.querySelector('div').innerHTML = jhtmls.render(function() { /*! for(var i = 0; i < 10; i++) { <h1>#{this} - #{i}</h1> } */ }, 'hello world!'); })(); </script> </body> </html>
如何对JavaScript代码进行base64编码?
var encodedData = window.btoa("Hello, world"); //encode var decodedData = window.atob(encodedData); //decode
目前除了Internet Explorer之外的现代浏览器都支持该方法(兼容性列表:http://caniuse.com/#search=atob,备注:感谢Zack__lin提出的指正,在iOS5中不支持atob和btoa)。如下代码即为我们通过atob解码之后的真实JavaScript代码:
void function(e) { "use strict"; function n(e) { return String(e).replace(/["<>& ]/g, function(e) { return "&" + o[e] + ";" }) } function t(e) { var n = []; return n.push("with(this){"), n.push(e.replace(/<(script|style)[^>]*>[\s\S]*?<\/\1>/g, function(e) { return ['!#{unescape("', escape(e), '")}'].join("") }).replace(/[\r\n]+/g, "\n").replace(/^\n+|\s+$/gm, "").replace(/^([ \w\t_$]*([^&\^?|\n\w\/'"{}\[\]+\-():; \t=\.$_]|:\/\/).*$|^(?!\s*(else|do|try|finally|void|typeof\s[\w$_]*)\s*$)[^'":;{}()\n|=&\/^?]+$)\s?/gm, function(e) { return e = e.replace(/&none;/g, "").replace(/["'\]/g, "\$&").replace(/\n/g, "\n").replace(/(!?#)\{(.*?)\}|(!?\$)([a-z_]+\w*(?:\.[a-z_]+\w*)*)/g, function(e, n, t, r, u) { if (r && (n = r, t = u), !t) return ""; t = t.replace(/\n/g, "\n").replace(/\([\'"])/g, "$1"); var o = /^[a-z$][\w+$]+$/i.test(t) && !/^(true|false|NaN|null|this)$/.test(t); return ["',", o ? ["typeof ", t, "==='undefined'?'':"].join("") : "", "#" === n || "$" === n ? "_encode_" : "", "(", t, "),'"].join("") }), e = ["'", e, "'"].join("").replace(/^'',|,''$/g, ""), e ? ["_output_.push(", e, ");"].join("") : "" })), n.push("}"), new Function("_output_", "_encode_", "helper", "jhtmls", n.join("")) } function r(e, r, o) { "function" == typeof e && (e = String(e).replace(/^[^\{]*\{\s*\/\*!?[ \f\t\v]*\n?|[ \f\t\v]*\*\/[;|\s]*\}$/g, "")); var i = t(e), c = function(e, t) { var r = []; return "undefined" == typeof t && (t = function(e) { i.call(e, r, n, t, u) }), i.call(e, r, n, t, u), r.join("") }; return arguments.length <= 1 ? c : c(r, o) } var u = u || {}, o = { '"': "quot", "<": "lt", ">": "gt", "&": "amp", " ": "nbsp" }; u.render = r, "function" == typeof define ? (define.amd || define.cmd) && define(function() { return u }) : "undefined" != typeof module && module.exports ? module.exports = u : window[e] = u }("jhtmls");
可以看到其实就是一个mini的模板引擎,实现有render方法,render方法会对传入的函数进行toString并解析其内容,然后执行渲染返回结果。
所以经过以上这番折腾,我们就能够看到页面循环10次输出"hello world!"和循环的索引。