9.1.1 用eval()方法進(jìn)行求值
eval()方法可能是在運(yùn)行時(shí)進(jìn)行代碼求值的最常用方式了。作為定義在全局作用域內(nèi)的eval()方法狞洋,該方法將在當(dāng)前上下文內(nèi)矗漾,執(zhí)行所傳入字符串形式的代碼毅臊。執(zhí)行返回結(jié)果則是最后一個(gè)表達(dá)式的執(zhí)行結(jié)果继低。
1)基本功能
該方法將執(zhí)行傳入代碼的字符串竹宋,在調(diào)用eval()方法的作用域內(nèi)進(jìn)行代碼求值劳澄。
示例9.1 eval()方法的基本測(cè)試
test?suite
#results?.pass{color:green;}
#results?.fail{color:red;}
function?assert(value,desc){
var?li?=?document.createElement('li');
li.className?=?value???'pass'?:?'fail';
li.appendChild(document.createTextNode(desc));
document.getElementById('results').appendChild(li);
}
assert(eval('5+5')?===?10,'5?and?5?is?10');
assert(eval('var?ninja?=?5;')?===?undefined,'no?value?was?returned.');
assert(ninja?===?5,'The?variable?ninja?was?created');
(function(){
eval('var?ninja?=?6;');
assert(ninja?===?6,'evaluated?within?the?current?scope.');
})()
assert(window.ninja?===?5,'this?global?scope?was?unaffected.');
assert(ninja?===?5,'the?global?scope?was?unaffected.');
2)求值結(jié)果
eval()方法將返回傳入字符串中最后一個(gè)表達(dá)式的執(zhí)行結(jié)果。
eval('3+4;5+6') 結(jié)果將返回11
任何不是簡(jiǎn)單變量蜈七、原始值秒拔、賦值語(yǔ)句的內(nèi)容都需要在外面包裝一個(gè)括號(hào),以便返回正確的結(jié)果飒硅。
var o = eval('({ninja:1})')
示例9.2 測(cè)試eval()的返回結(jié)果
test?suite
#results?.pass{color:green;}
#results?.fail{color:red;}
function?assert(value,desc){
var?li?=?document.createElement('li');
li.className?=?value???'pass'?:?'fail';
li.appendChild(document.createTextNode(desc));
document.getElementById('results').appendChild(li);
}
var?ninja?=?eval('({name:"ninja"})');
assert(ninja?!=?undefined,'the?ninja?was?created');
assert(ninja.name?===?'ninja','and?with?the?expected?property');
var?fn?=?eval('(function(){return?"ninja";})');
assert(typeof?fn?===?'function','the?function?as?created');
assert(fn()?===?'ninja','and?returns?expected?value');
var?ninja2?=?eval('{name:"ninja"}');
assert(ninja2?!=?undefined,'ninja2?was?created.');
assert(ninja2.name?===?'ninja','and?with?the?expected?property');
最后一個(gè)測(cè)試失敗了砂缩,因?yàn)閷?duì)象沒(méi)有按照預(yù)期進(jìn)行創(chuàng)建。
就像我們用普通方式在特定作用域內(nèi)創(chuàng)建函數(shù)一樣三娩,eval()創(chuàng)建的函數(shù)會(huì)繼承該作用域的閉包——局部作用域內(nèi)執(zhí)行eval()的衍生結(jié)果庵芭。
9.1.2 用函數(shù)構(gòu)造器進(jìn)行求值
js中所有的函數(shù)都是Function的實(shí)例,可以通過(guò)像function name(){}這樣的語(yǔ)法創(chuàng)建命名函數(shù)雀监,或者省略名稱創(chuàng)建匿名函數(shù)双吆。
也可以直接使用Function構(gòu)造器來(lái)實(shí)例化函數(shù)。
var add = new Function('a','b','return a+b');
assert(add(3,4)===7,'Function created and working!);
Function構(gòu)造器可變參數(shù)列表的最后一個(gè)參數(shù)滔悉,始終是要?jiǎng)?chuàng)建函數(shù)的函數(shù)體內(nèi)容伊诵。前面的參數(shù)則表示函數(shù)的形參名稱。
上邊代碼等價(jià)于: var add = function(a,b){return a+b}
雖然這些代碼在功能上是等同的回官,但采用Function構(gòu)造器方式有一個(gè)明顯的區(qū)別曹宴,函數(shù)體由運(yùn)行時(shí)的字符串所提供。
另外一個(gè)極其重要的實(shí)現(xiàn)區(qū)別是歉提,使用Function構(gòu)造器創(chuàng)建函數(shù)的時(shí)候笛坦,不會(huì)創(chuàng)建閉包。在不想承擔(dān)任何不相關(guān)的閉包的開(kāi)銷時(shí)苔巨,這可能是一件好事版扩。
9.1.3 用定時(shí)器進(jìn)行求值
通過(guò)定時(shí)器可以讓代碼字符串進(jìn)行求值,而且是異步的侄泽。
我們通常給定時(shí)器傳遞一個(gè)內(nèi)聯(lián)函數(shù)或函數(shù)引用礁芦。這是setTimeout()和setInterval()方法推薦使用的方式,但是這些方法也可以接受字符串的傳入悼尾,從而在定時(shí)器觸發(fā)的時(shí)候進(jìn)行求值柿扣。
var tick = window.setTimeout('alert("hi")',100)
這種方式使用情況不多,除非要求值的代碼必須是運(yùn)行時(shí)字符串闺魏。
9.1.4全局作用域內(nèi)的求值操作
示例9.3 在全局作用域內(nèi)求值代碼
test?suite
#results?.pass{color:green;}
#results?.fail{color:red;}
function?assert(value,desc){
var?li?=?document.createElement('li');
li.className?=?value???'pass'?:?'fail';
li.appendChild(document.createTextNode(desc));
document.getElementById('results').appendChild(li);
}
function?globalEval(data){
data?=?data.replace(/^\s*|\s*$/g,'');
if(data){
var?head?=?document.getElementsByTagName('head')[0]||document.documentElement,
script?=?document.createElement('script');
script.type?=?'text/javascript';
script.text?=?data;
head.appendChild(script);
head.removeChild(script);
}
}
window.onload?=?function(){
(function(){
globalEval('var?test=5;');
})()
assert(test===5,'The?code?was?evaluated?globally.')
}
9.1.5 安全的代碼求值
一個(gè)命名為Caja的谷歌項(xiàng)目未状,嘗試創(chuàng)建一個(gè)js翻譯器,以便將js轉(zhuǎn)換成一種更安全且免受惡意攻擊的形式析桥。
http://code.google.com/p/google-caja/
9.2 函數(shù)反編譯
示例9.4 將函數(shù)反編譯成字符串
function?test(a){return?a+a;}
assert(test.toString()==='function?test(a){return?a+a;}','function?decompiled')
toString()的返回值包含原始聲明的所有空格司草,包括行結(jié)束符艰垂。請(qǐng)注意,在反編譯函數(shù)的時(shí)候埋虹,需要考慮空格和函數(shù)體的格式猜憎。
反編譯行為有很多潛在的用途,尤其是在宏指令和代碼重寫(xiě)的時(shí)候搔课。在Prototype js庫(kù)中拉宗,有一個(gè)比較有趣的應(yīng)用是,將函數(shù)進(jìn)行反編譯從而讀取該函數(shù)的參數(shù)辣辫,然后將這些參數(shù)名稱保存到一個(gè)數(shù)組中旦事。通常用于確定函數(shù)想得到什么樣的參數(shù)值。
示例9.5 查找函數(shù)參數(shù)名稱的函數(shù)
test?suite
#results?.pass{color:green;}
#results?.fail{color:red;}
function?assert(value,desc){
var?li?=?document.createElement('li');
li.className?=?value???'pass'?:?'fail';
li.appendChild(document.createTextNode(desc));
document.getElementById('results').appendChild(li);
}
function?argumentNames(fn){
var?found?=?/^[\s\(]*function[^(]*\(\s*([^)]*?)\s*\)/.exec(fn.toString());
return?found?&&?found[1]???found[1].split(/,\s*/)?:?[];
}
assert(argumentNames(function(){}).length?===?0,'works?on?zero-arg?functions.')
assert(argumentNames(function(x){})[0]?===?'x','single?argument?working.')
var?results?=?argumentNames(function(a,b,c,d,e){});
assert(results[0]?==?'a'?&&?results[1]?==?'b'?&&?results[2]?==?'c'?&&?results[3]?==?'d'?&&?results[4]?==?'e','multiple?arguments?working.')
該函數(shù)反編譯了傳入的函數(shù)急灭,并使用正則表達(dá)式姐浮,將這些參數(shù)從逗號(hào)分隔的參數(shù)列表中抽取出來(lái)。
9.3 代碼求值實(shí)戰(zhàn)
9.3.1 JSON轉(zhuǎn)化
示例9.6 將JSON字符串轉(zhuǎn)化成js對(duì)象
var?json?=?'{"name":"ninja"}';
var?object?=?eval('('+json+')');
assert(object.name?===?'ninja','my?name?is?ninja!');
使用eval()做JSON解析時(shí)需要注意的主要是:通常葬馋,JSON數(shù)據(jù)來(lái)自于遠(yuǎn)程服務(wù)器卖鲤,盲目執(zhí)行遠(yuǎn)程服務(wù)器上不可信代碼,基本是不可取的畴嘶。
最受歡迎的JSON轉(zhuǎn)換器腳本是由JSON標(biāo)記的創(chuàng)造者所編寫(xiě)的蛋逾,在該轉(zhuǎn)換器中,他做了一些初步的JSON字符串解析窗悯,以防止任何惡意信息通過(guò)区匣。代碼地址:https://github.com/douglascrockford/JSON-js
他寫(xiě)的函數(shù)在實(shí)際求值之前,執(zhí)行一些重要的預(yù)處理操作蒋院。
.防范一些可能在某些瀏覽器上引起問(wèn)題的Unicode字符亏钩。
.防范惡意顯示的非JSON內(nèi)容,包括賦值運(yùn)算符和new操作符欺旧。
.確保只包含了符合JSON規(guī)范的字符姑丑。
9.3.2 導(dǎo)入有命名空間的代碼
對(duì)于將命名空間導(dǎo)入到當(dāng)前上下文,base2庫(kù)提供了一個(gè)非常有趣的解決方案辞友。因?yàn)闆](méi)有辦法將該問(wèn)題進(jìn)行自動(dòng)化操作栅哀,因此我們可以利用運(yùn)行時(shí)求值讓該實(shí)現(xiàn)變得簡(jiǎn)單。
每當(dāng)一個(gè)新類或模塊添加到base2包的時(shí)候称龙,構(gòu)造可執(zhí)行代碼的字符串留拾,對(duì)其進(jìn)行求值,可以將產(chǎn)生的函數(shù)引入到當(dāng)前上下文中茵瀑,示例如下间驮,假設(shè)已經(jīng)加載了base2躬厌。
示例9.7 測(cè)試base2的命名空間導(dǎo)入是如何工作的马昨。
base2.namespace?==??????????????????????????????????????????//#1
"var?Base=base2.Base;var?Package=base2.Package;"?+
"var?Abstract=base2.Abstract;var?Module=base2.Module;"?+
"var?Enumerable=base2.Enumerable;var?Map=base2.Map;"?+
"var?Collection=base2.Collection;var?RegGrp=base2.RegGrp;"?+
"var?Undefined=base2.Undefined;var?Null=base2.Null;"?+
"var?This=base2.This;var?True=base2.True;var?False=base2.False;"?+
"var?assignID=base2.assignID;var?detect=base2.detect;"?+
"var?global=base2.global;var?lang=base2.lang;"?+
"var?JavaScript=base2.JavaScript;var?JST=base2.JST;"?+
"var?JSON=base2.JSON;var?IO=base2.IO;var?MiniWeb=base2.MiniWeb;"?+
"var?DOM=base2.DOM;var?JSB=base2.JSB;var?code=base2.code;"?+
"var?doc=base2.doc;";
assert(typeof?This?===?"undefined",??????????????????????????//#2
"The?This?object?doesn't?exist."?);
eval(base2.namespace);???????????????????????????????????????//#3
assert(typeof?This?===?"function",???????????????????????????//#4
"And?now?the?namespace?is?imported."?);
assert(typeof?Collection?===?"function",
"Verifying?the?namespace?import."?);
這是一個(gè)用于解決復(fù)雜問(wèn)題的非常巧妙的方法竞帽。
9.3.3 JS壓縮和混淆
最好是將代碼寫(xiě)得越清晰越好,然后再進(jìn)行壓縮傳輸鸿捧。
壓縮js代碼的工具 packerhttp://dean.edwards.name/packer/使用eval()進(jìn)行大規(guī)模的重寫(xiě)和解壓
下載和求值之間的組合對(duì)頁(yè)面的性能才是最重要的屹篓。
加載時(shí)間 = 下載時(shí)間+求值時(shí)間
使用簡(jiǎn)單壓縮性能是最好的,如果要用代碼混淆匙奴,可以使用packer
9.3.4 動(dòng)態(tài)重寫(xiě)代碼
由于我們可以使用函數(shù)的toString()方法反編譯現(xiàn)有的js函數(shù)堆巧,可以從現(xiàn)有函數(shù)中提取并加工原有函數(shù)的內(nèi)容,從而創(chuàng)建一個(gè) 新函數(shù)泼菌。
單元測(cè)試庫(kù)Screw.Unit(https://github.com/nkallen/screw-unit)谍肤,就是一個(gè)這樣的案例。
Screw.Unit使用庫(kù)中提供的函數(shù)哗伯,將現(xiàn)有測(cè)試函數(shù)中的內(nèi)容進(jìn)行了動(dòng)態(tài)重寫(xiě)荒揣。
describe('Matchers',function(){
it('invokes?the?provided?matcher?on?a?call?to?expect',function(){
expect(true).to(equal,true);
expect(true).to_not(equal,false);
})
})
describe(),it()以及expect(),這些方法在全局作用域內(nèi)都不存在焊刹。Screw.Unit重寫(xiě)了這段代碼系任,使用多個(gè)width(){}語(yǔ)句,將函數(shù)內(nèi)部的內(nèi)容注入到需要執(zhí)行的函數(shù)中虐块。
var?contents?=?fn.toString().match(/^[^{]*{((.*\n*)*)}/m)[1];
var?fn?=?new?Function('matchers','specifications','with(specifications){width(matchers){'+contents+'}}')
fn.call(this.Screw.Matchers,Screw.specifications);
這是一個(gè)讓測(cè)試開(kāi)發(fā)人員在無(wú)需將變量引入到全局作用域的情況下俩滥,利用代碼求值就可以提供簡(jiǎn)潔用戶體驗(yàn)功能的場(chǎng)景。
9.3.5 面向切面的腳本標(biāo)簽
AOP贺奠,面向方面編程霜旧。
AOP技術(shù)可以在運(yùn)行時(shí)將代碼進(jìn)行注入并執(zhí)行一些“橫切”代碼,如日志記錄儡率、緩存颁糟、安全性檢查等。AOP引擎將在運(yùn)行時(shí)添加日志代碼喉悴,而不是在原有代碼中添加大量的日志語(yǔ)句棱貌,以便讓開(kāi)發(fā)人員在開(kāi)發(fā)期間不用關(guān)注這些事情。
定義自定義腳本類型是非常簡(jiǎn)單的箕肃,因?yàn)闉g覽器會(huì)忽略任何無(wú)法識(shí)別的腳本類型婚脱。通過(guò)使用一個(gè)不標(biāo)準(zhǔn)的類型值,我們可以強(qiáng)制瀏覽器完全忽視一個(gè)腳本塊勺像。
...
注意障贸,我們使用統(tǒng)一約定的“x”表示自定義類型。我們打算用這樣的塊來(lái)包含正常的js代碼吟宦,以便在頁(yè)面加載時(shí)進(jìn)行執(zhí)行篮洁,而不是通常的內(nèi)聯(lián)執(zhí)行。
示例9.8 創(chuàng)建一個(gè)在頁(yè)面加載后才執(zhí)行的腳本標(biāo)簽類型
test?suite
#results?.pass{color:green;}
#results?.fail{color:red;}
function?assert(value,desc){
var?li?=?document.createElement('li');
li.className?=?value???'pass'?:?'fail';
li.appendChild(document.createTextNode(desc));
document.getElementById('results').appendChild(li);
}
function?globalEval(data){
data?=?data.replace(/^\s*|\s*$/g,'');
if(data){
var?head?=?document.getElementsByTagName('head')[0]||document.documentElement,
script?=?document.createElement('script');
script.type?=?'text/javascript';
script.text?=?data;
head.appendChild(script);
head.removeChild(script);
}
}
window.onload?=?function(){
var?scripts?=?document.getElementsByTagName('script');
for(var?i=0;?i
if(scripts[i].type?==?'x/onload'){
globalEval(scripts[i].innerHTML)
}
}
}
assert(true,'Executed?on?page?load')
在本例中殃姓,我們提供一個(gè)瀏覽器忽略執(zhí)行的自定義腳本塊袁波。在頁(yè)面的onload處理程序中瓦阐,查詢所有的腳本塊,再篩選自定義類型的腳本塊篷牌,最后用本章前面開(kāi)發(fā)的globalEval()函數(shù)睡蟋,在全局作用域內(nèi)對(duì)腳本塊的內(nèi)容進(jìn)行求值。
這種技術(shù)有更復(fù)雜更有意義的用途枷颊。例如戳杀,將自定義腳本塊和jQuery.tmpl()方法一起使用,用于提供運(yùn)行時(shí)模板夭苗。利用它可以在用戶界面上執(zhí)行腳本信卡,或者在準(zhǔn)備操作DOM的時(shí)候,甚至是相鄰元素上執(zhí)行腳本题造。
9.3.6 元語(yǔ)言和領(lǐng)域特定語(yǔ)言
關(guān)于運(yùn)行時(shí)代碼求值的一個(gè)最重要示例坐求,可以在構(gòu)建于js之上的其他編程語(yǔ)言實(shí)現(xiàn)中看到:元語(yǔ)言∩卫妫可以將其動(dòng)態(tài)轉(zhuǎn)換成js源代碼并求值桥嗤。通常,這種定制語(yǔ)言非常特定于開(kāi)發(fā)人員的業(yè)務(wù)需求仔蝌,并且已經(jīng)創(chuàng)建了領(lǐng)域特定語(yǔ)言(DSL)這樣的名字泛领。
Processing.js
Processing.js是Processing(http://processing.org/)可視化語(yǔ)言的一部分,該可視化語(yǔ)言通常使用java實(shí)現(xiàn)敛惊。js的實(shí)現(xiàn)運(yùn)行在HTML5的Canvas元素上渊鞋,由John Resig創(chuàng)建。
這種實(shí)現(xiàn)是一種完整的編程語(yǔ)言瞧挤,可以用來(lái)操作繪圖區(qū)域的視覺(jué)顯示锡宋。
通過(guò)使用Processing.js語(yǔ)言,我們獲得一些使用js時(shí)所沒(méi)有的直接好處特恬。
.從Processing高級(jí)語(yǔ)言特性中獲益(如類和繼承)
.獲取Processing的簡(jiǎn)單但強(qiáng)大的繪圖API
.可以使用Processing現(xiàn)有的文檔和示例执俩。
所以這些高級(jí)處理代碼,都可以通過(guò)js語(yǔ)言的代碼求值功能來(lái)實(shí)現(xiàn)癌刽。
Objective-J
是Objective-C編程語(yǔ)言的js實(shí)現(xiàn)役首,被用于280Slides產(chǎn)品。
Objective-J解析程序显拜,是由js編寫(xiě)的衡奥,并可以在運(yùn)行階段轉(zhuǎn)換Objective-J代碼,它們使用輕量級(jí)表達(dá)式進(jìn)行匹配并處理Objective-C語(yǔ)法代碼远荠,而不會(huì)干擾現(xiàn)有的js代碼矮固。其處理結(jié)果是一個(gè)js代碼字符串,用于在運(yùn)行時(shí)進(jìn)行求值操作譬淳。