在Javascript中,函数可以很容易的被序列化(字符串化),也就是得到函数的源码.但其实这个操作的内部实现(引擎实现)并不是你想象的那么简单.SpiderMonkey中一共使用过两种函数序列化的技术:一种是利用反编译器(decompiler)将函数编译后的字节码反编译成源码字符串,另一种是在将函数编译成字节码之前就把函数源码压缩并存储下来,用到的时候再解压还原.
如何进行函数序列化 在SpiderMonkey中,能将函数序列化的方法或函数有三个:Function.prototype.toString,Function.prototype.toSource,uneval.只有toString方法是标准的,也就是各引擎通用的.但是ES标准中关于Function.prototype.toString方法的规定(ES5 15.3.4.2)只有寥寥数语,也就是说,基本没有标准,引擎自己决定该如何实现.
函数序列化的作用 函数序列化最主要的作用应该是利用序列化生成的函数源码来重新定义这个函数.
复制代码 代码如下:
function a() {
...
alert("a")
...
}
a() //执行时可能会弹出"a"
a = eval("(" + a.toString().replace('alert("a")', 'alert("b")') + ")")
a() //执行时可能会弹出"b"
你也许会想:"我写了这么多年Javascript,怎么没有遇到这种需求".的确,如果是自己的网站,自己完全控制的js文件,不需要以这种打补丁的方式来修改函数,直接修改就可以了.但是如果源文件不是你能控制的了的话,就很有可能要这样做了.比如常用的地方有greasemonkey脚本:你可能需要禁用或修改某个网站中的某个函数.还有就是Firefox扩展:你需要修改Firefox自身的某个函数(可以说Firefox是用JS写的).举个我自己写的
Firefox脚本的例子:
复制代码 代码如下:location == "chrome://browser/content/browser.xul" && eval("gURLBar.handleCommand=" + gURLBar.handleCommand.toString().replace(/^\s*(load.+);/gm, "/^javascript:/.test(url)||(content.location=='about:blank'||content.location=='about:newtab')?$1:gBrowser.loadOneTab(url,{postData:postData,inBackground:false, allowThirdPartyFixup: true});"))
这个代码的作用是:在地址栏上回车时,让Firefox在新标签中打开页面,而不是占用当前标签.实现方式就是用toString方法读取到gURLBar.handleCommand函数的源码,然后用正则替换后传给eval,重新定义了这个函数.
为什么不用直接定义的方式,也就是直接重写函数呢:
gURLBar.handleCommand = function(){...//将原本的函数更改了一个小地方}
不能这么做的原因是因为我们得考虑兼容性,我们应该尽可能小的更改这个函数的源码.如果这么写的话,Firefox的gURLBar.handleCommand源码一旦发生变化,这个脚本就失效了.比如Firefox3和Firefox4中都有这个函数,但函数内容差别非常大,可是如果用正则替换部分关键字的话,只要这个被替换的这个关键字没有发生变化的话,就不会出现不兼容的现象.
反编译字节码 在SpiderMonkey中,函数在被解析之后会被编译成字节码(bytecode),也就是说,内存中存储着并不是原始的函数源码.SpiderMonkey中存在一个反编译器,它的主要作用就是把函数的字节码反编译成函数源码的形式.
在Firefox16以及之前的版本中,SpiderMonkey使用的就是这种方法,如果你使用的是这些版本的Firefox的话,可以尝试下面的代码:
复制代码 代码如下:
alert(function () {
"字符串";
//注释
return 1 + 2 + 3
}.toString())
返回的字符串是
function () {
return 6;
}
输出和其他的浏览器完全不同:
1.没有意义的原始值字面量在编译的时候会被删除,这个例子中就是"字符串". 你也许会觉得:"貌似没什么问题,反正这些值对于函数的运行来说并没有什么意义".等等,你是不是忘了个东西,表示严格模式的字符串"use strict"怎么办呢?
在不支持严格模式的版本中,比如Firefox3.6,这个"use strict"和其他字符串没什么区别,编译的时候会被删除.在SpiderMonkey实现了严格模式之后,虽然编译的时候同样会忽略掉这个字符串"use strict",但在反编译的时候会进行判断,如果这个函数处于严格模式中,则会在函数体的第一行添加上"use strict",下面是对应的引擎源码.
static JSBool
复制代码 代码如下:
DecompileBody(JSPrinter *jp, JSScript *script, jsbytecode *pc)
{
/* Print a strict mode code directive, if needed. */
if (script->strictModeCode && !jp->strict) {
if (jp->fun && (jp->fun->flags & JSFUN_EXPR_CLOSURE)) {
/*
* We have no syntax for strict function expressions;
* at least give a hint.
*/
js_printf(jp, "\t/* use strict */ \n");
} else {
js_printf(jp, "\t\"use strict\";\n");
}
jp->strict = true;
}
jsbytecode *end = script->code + script->length;
return DecompileCode(jp, script, pc, end - pc, 0);
}
2.注释在编译的时候也会被删除 这个貌似没太大影响,不过有些人愿意利用函数注释来实现多行字符串,这个方法在Firefox 17之前的版本中是不可用的.
复制代码 代码如下:
function hereDoc(f) {
return f.toString().replace(/^.+\s/,"").replace(/.+$/,"");
}
var string = hereDoc(function () {/*
我
你
他
*/});
console.log(string)
我
你
他
3.原始值字面量的运算会在编译时进行.
这算是一种优化方式,《高性能JavaScript》提到过:
反编译的弊端 由于新技术的出现(比如严格模式)以及在修改其他相关bug的时候,反编译器这部分的实现经常需要更改,更改就有可能产生新的bug,我自己就亲身遇到过一个bug.大概是在Firefox10左右的时候,具体问题记不大清了,反正是关于反编译时小括号是否要保留的问题,大概是这样的:
复制代码 代码如下:
>(function (a,b,c){return (a+b)+c}).toString()
"function (a, b, c) {
return a + b + c;
}"
在反编译时,(a+b)中的小括号被省略了,由于加法结合律从左到右,所以这没关系.但我遇到的bug是这样的:
复制代码 代码如下:
>(function (a,b,c){return a+(b+c)}).toString()
"function (a, b, c) {
return a + b + c;
}"
这就就不行了,a+b+c不等于a+(b+c),比如在a=1,b=2,c="3"的情况下,a+b+c等于"33",而a+(b+c)等于"123".
关于反编译器,Mozilla工程师Luke Wagner指出,反编译器对他们实现一些新功能的阻碍很大,而且经常会出现一些bug:
Not to pile on, but I too have felt an immense drag from the decompiler in the last year. Testing coverage is also poor and any non-trivial change inevitably produces fuzz bugs.The sooner we remove this drag the sooner we start reaping the benefits. In particular,I think now is a much better time to remove it than after doing significant frontend/bytecode hacking for new language features.
Brendan Eich也表示,反编译器的确有很多不理想:
I have no love for the decompiler, it has been hacked over for 17 years. 存储函数源码
从Firefox17之后,SpiderMonkey改成了第二种实现方法,其他浏览器也应该是这样实现的吧.函数序列化得到的字符串完全和源码一致,包括空白符,注释等等.这样的话,大部分问题就应该没有了吧.不过,貌似我又想到个问题.还是关于严格模式的.
比如:
复制代码 代码如下:
(function A() {
"use strict";
alert("A");
}) + ""
当然,返回的源码中也应该有"use strict",所有浏览器都是这么实现的:
复制代码 代码如下:
function A() {
"use strict";
alert("A");
}
但如果是这样呢:
复制代码 代码如下:
(function A() {
"use strict";
return function B() {
alert("B")
}
})() + ""
内部函数B也处于严格模式中,输出B的函数源码应不应该加上"use strict"呢.试验一下:
上面说了,Firefox17之前Firefox4之后的版本是通过判断当前函数是否处于严格模式来决定输出不输出"use strict"的,函数B继承了函数A的严格模式,所以会有"use strict".
同时函数源码是缩进严格的,因为在反编译的时候,SpiderMonkey会给反编译出的源码进行格式化,即使之前的源码完全没有缩进也没关系:
复制代码 代码如下:
function B() {
"use strict";
alert("B");
}
Firefox17之后的版本会不会带有"use strict"呢?因为是直接把函数源码保存下来的,而且函数B中的确没有"use strict"字样.试验结果是:会添加上"use strict",只是缩进有点问题,因为没有格式化这一步了.
复制代码 代码如下:
function B() {
"use strict";
alert("B")
}
SpiderMonkey最新版的jsfun.cpp源码中有对应的注释
// 如果一个函数的某个上层函数中拥有"use strict",那么这个函数就继承了上层函数的严格模式.
// 我们也会在这个内部函数的函数体内插入"use strict".
// 这就确保了,如果这个函数的toString方法的返回值被重新求值时,
// 重新生成的函数会和原函数有着相同的语义.
而不同的是,其他浏览器都是不带"use strict"的:
复制代码 代码如下:
function B() {
alert("B")
}
虽然这不会有什么太大影响,但我觉的Firefox的实现是更合理的.
相关推荐:
在线AI写文:开启高效创作新时代
英语日记AI生成:轻松提升英语水平的智能助手
SEO查:如何通过精准优化让网站流量飞速增长,吉林推广营销怎么样
OpenArtAI如何进入:数字艺术的未来之门,分析海报ai
SEO查看-如何通过精确分析提升网站流量与排名,东丽网站建设价格
AI软件不用登录,让你的工作更高效轻松,智能ai写作改稿怎么改
ChatGPT免登录:轻松畅聊,无需注册,快速体验AI智能助手,眼泪ai
Chatttst:开启智能沟通新时代的无限可能,上海联通ai
SEO要点:提升网站排名的核心技巧与策略,福田市网站建设推广费用
SEO组织:让您的网站流量倍增的秘密武器,seo 绩效标准
SEO主要是为网站引流吗?深入解析SEO的核心价值,AI算算
seo需要买什么,seo需要考虑什么 ,ai做表头
《收录情况:数字时代的网络信息检索与价值体现》,山东全域营销推广软件客服电话
主题导航-引领互联网世界的智慧之路,大渡口网站建设方案
什么是seo运营,什么是seo营销 ,ai绘画沙地
SEO优化10种策略:提升网站排名的有效方法,帅气ai男头白底
仿写AI:智能时代的创作革命,洛江区移动房网站推广
SEO量:提升网站流量和排名的关键法则,德兴seo
SEO新人必读:从零开始SEO的核心技巧与实践指南,网站邮件推广设计
洗文章AI:让内容创作变得更智能、更高效
seo进阶买什么书运营,seo入门难吗 ,没有ai软件怎么打开ai图片
SEO舆情:如何通过有效的舆情管理提升企业品牌形象,seo推广外包提高收录
SEO关键词的选择步骤:让你的网站在搜索引擎中脱颖而出,ai书法化
AI热门软件,未来科技的钥匙
SEO优化是做什么的?让你的网站流量飙升的秘密武器,乳腺癌ai作用机理
什么是亚马逊seo策略,亚马逊的seo推广是什么 ,天天学术ai写作优惠券
ChatGPT40不收费版本:科技革新,智能助手助力生活与工作,ai医疗武器有哪些
用AI批量下载工具,高效管理你的文件和资源
为什么要监控SEO效果,国家为什么要监控个人 ,抚顺ai系统
软件我在AI:改变未来的智能助手
SEO优化大全:让你的网站排名轻松破局,精准引流更高效!,274357524ai
SEO场景下的数字营销:如何通过精准优化提升网站流量,陕西融发建设集团网站
SEO埋词技巧,如何提升网站排名?,贵州营销推广方法
如何通过AI写文章,轻松提高写作效率与质量
ChatGPT:我目前无法查看或解析附件,您是否遇到过这样的困扰?,ai+燃烧
seo用什么写属性写,seo属于什么 ,芭田科学施肥AI
ChatGPT为什么页面下拉不了?问题解析与解决方案,ai对准
SEO手段:提升网站流量的制胜法宝,idc网站怎么推广
优化*:打造更加高效、创新的娱乐体验,石排网站建设制作多少钱
SEO怎么排名?这5大技巧,轻松提升网站排名,人人都可ai
SEO优化关键技巧:提升网站排名的实战攻略,科大讯飞ai论文写作软件
OpenAI银行卡扣款的公司是哪家?揭秘背后的支付流程与安全保障,AI素描相片
优化平台:让数字化转型更简单、更高效,莆田谷歌seo品牌排行
ChatPartner无法连接网络?解决方案,让你的聊天体验更顺畅!,如何用ai给自己写作文
为什么新手做seo好做,为什么要懂seo ,ai少女 3060显卡
ChatGPT手机下载后打不开?可能是这些问题导致的,解决方法在这里!,孤城ai动画
seo网站是什么找行者SEO,seo分析网站 ,ai图文梅花
ChatGPT异常了:人工智能的极限与突破,ai and ethics
优方法-高效生活与工作的秘密武器,钻石营销推广方案
2025百度收录优化:提升网站排名,助力企业数字化转型,第一ai做