在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的实现是更合理的.

本文原创发布php中文网,转载请注明出处,感谢您的尊重!

spidermonkey php,javascript SpiderMonkey中的函数序列化如何进行_基础知识相关推荐

  1. php js绝对路径,javascript将相对路径转绝对路径示例_基础知识

    这里介绍的其实本质上是两种方法,通过创建DOM或通过JavaScript计算: 1)通过新创建的Image, 经测试会发送一个Aborted的请求,并且IE6不支持, 将new Image改成docu ...

  2. 开发过程中js遇到的问题及一些基础知识恶补

    Supermarket 开发过程中js遇到的问题及一些基础知识恶补 函数相关问题 DOM元素相关操作 变量操作 异常捕获 开发过程中关于条形码识别 开发结束关于前端板块思路 函数相关问题 (funct ...

  3. python中哪个函数能生成集合_神奇的python系列11:函数之生成器,列表推导式

    1.生成器 生成器的本质是迭代器. 在python中有三种方式来获取生成器 1.通过生成器函数 2.通过各种推到式来实现生成器 3.通过数据的转换也可以获取生成器 #函数 deffunc():prin ...

  4. 中给函数赋读权限_教你如何使用MCGS昆仑通态设置密码增加权限

    大家好!我们在工业生产过程控制中,为了尽量避免由于现场人为的误操作所引发的故障或事故,而某些误操作所带来的后果有可能是致命性的.为了防止这类事故的发生,所以需要严格限制各类操作的权限,使不具备操作资格 ...

  5. python中groupby()函数讲解与示例_详解python中groupby函数通俗易懂

    一.groupby 能做什么? python中groupby函数主要的作用是进行数据的分组以及分组后地组内运算! 对于数据的分组和分组运算主要是指groupby函数的应用,具体函数的规则如下: df[ ...

  6. python中int函数是什么作用_【后端开发】python中int函数怎么用

    int() 函数用于将一个字符串会数字转换为整型.接下来通过本文给大家介绍python 中的int()函数的相关知识,感兴趣的朋友一起看看吧 int(x, [base]) 功能: 函数的作用是将一个数 ...

  7. python中get函数是什么意思_详解python中get函数的用法(附代码)_后端开发

    strncmp函数用法详解_后端开发 strncmp函数为字符串比较函数,其函数语法为"int strncmp ( const char * str1, const char * str2, ...

  8. python中format函数用法简书_从Python安装到语法基础,这才是初学者都能懂的爬虫教程...

    Python和PyCharm的安装:学会Python和PyCharm的安装方法 变量和字符串:学会使用变量和字符串的基本用法 函数与控制语句:学会Python循环.判断语句.循环语句和函数的使用 Py ...

  9. python中print函数输出是字符串_如何用python3输出print函数?

    最近函数讲的比较多,小编知道大家对函数的热情很高,所以趁热打铁想扩展一些知识内容.毕竟大家对print函数不算陌生,用的也算是频繁.不知道大家有没有学过输出print函数方面的知识,今天小编要带来的内 ...

最新文章

  1. [转]Web API Introduction to OData Services using ASP.NET Web API
  2. 谷歌大脑2017总结下篇:从医疗、机器人等6个领域开始的改变世界之旅
  3. 如何自动设计多流网络 实现GPT3大规模神经网路同等规模的网络
  4. 模拟浏览器自动化测试工具Selenium之五Centos系统命令行下部署selenium环境试验
  5. 总结了点React,咱也不敢说
  6. vscode 如何实时显示html文件?
  7. LeetCode:63. 不同路径 II
  8. AIX 系统中 PVID 的含义与作用
  9. go build不从本地gopath获取_跟我一起学习go语言,包依赖管理工具go mod
  10. 鸿蒙系统可以替代安卓吗,华为今天发布的鸿蒙系统,到底能不能替代安卓?
  11. vue项目创建步骤 和 路由router知识点
  12. 4-3 面向复用的设计模式
  13. 电子健康行业调研报告 - 市场现状分析与发展前景预测(2021-2027年)
  14. jsp校园二手交易平台的设计答辩PPT模板
  15. 音乐播放器 EasyMusic (一)
  16. Prototype对象
  17. 用 ASTERISK 搭建自己的免费 VOIP 服务器
  18. MATLAB机器人运动学与动力学(自己学习用)
  19. pypyodbc 连接Access数据库常见报错整理
  20. 开源开放 | 欧若科技通过 OpenKG 开放 Nebula Graph 图数据库

热门文章

  1. 事件代理(冒泡机制)
  2. SD卡/SD卡卡槽/TF卡/TF卡卡槽的引脚定义
  3. 物联网毕业设计 NBIOT远程通信系统
  4. 计算机管理msc,win7系统打开计算机管理(compmgmt.msc)的操作方法
  5. php 手机摄像头,手机摄像头大光圈有什么作用
  6. [OpenCV] cv::VideoCapture中read和grab+retrieve的区别
  7. 理解WebKit和Chromium: WebKit的CSS实现
  8. 腾讯正式放弃独家版权,但欢呼“听歌自由”为时尚早
  9. NPDP产品经理小知识:新产品开发中的管道管理
  10. 思科查看IP和MAC及交换机端口的命令