代码求值机制
javaScript有几种运行机制,每种都有不同的使用上下文,这些不同的代码求值方式包括:
- eval函数
- 函数构造器
- 定时器
- script标签元素
在查看这些机制时,我们将讨论代码求值的作用域,并学习在运行时运行代码求值的安全实践。
用eval()方法进行求值
eval方法可能是运行时代码求值的最常用的方式了,作为定义在全局作用域内的eval方法,该方法将在当前上下文内,执行所传入字符串形式的代码。执行返回结果则是最后一个表达式的执行结果。
测试上面代码,感受eval运行。
应该指出的是,任何不是简单变量,原始值,赋值语句的内容都需要在外面包一个括号,以便返回正确的结果。例如,如果我们想使用eval()创建一个简单的对象,可能会编写如下的语句:
但是结果不是我们期望的,需要加一个括号,如下:
我们创建函数通常不会使用eval的方式,但是如果我们不知道要创建的函数具体的什么内容,我们可能需要在运行时生成代码或者从别人哪里得到一些代码。
就像我们用普通方式在特定作用域内创建函数一样,eval()创建的函数会继承该作用域的闭包,局部作用域内执行eval()时的衍生结果。
用函数构造器进行求值
javaScript中所有的函数都是Function的实例,可以直接使用Function构造器来实例化函数,如下所示:
Function构造器可变参数列表的最后一个参数,始终是要创建函数的函数题内容。前面的参数则表示函数的形参名称。所以,上述等价如下:
虽然功能等同,但采用Function构造器方式有一个明显的区别,函数体由运行时字符串所提供。
另一个重要区别是使用Function构造器创建函数的时候,不会创建闭包。在不想承担任何不相关闭包的开销时,这可是一件好事。
用定时器进行求值
定时器可以传递一个内联函数或函数引用,这是推荐使用的方式,但是这些方法也可以接受字符串的传入,从而在定时器触发的时候进行求值。示例如下:
使用这种方式的情形很罕见(大致相当于new Function()的使用方式)
,他的使用会让人失望,除非要求值的代码必须是运行时字符串。
因为断网就写到这里来了。
在讨论eval()方法时候我们强调,求值执行的作用域就是调用eval()时的作用域。
这段代码很简单,我们定义了一个名为globalEval()的函数,以便在全局作用域内求值任何想要求值的内容。
安全的代码求值
关于代码求值,经常出现的一个问题是,如何安全的执行javaScript代码,换句话说,在不损害网站完整性的情况下,可以安全的执行不可信的javaScript代码码?
然而还是有希望的,一个命名为caja的谷歌项目,尝试创建一个javaScript翻译器,以便将javaScript转换成一种更安全且免受恶意攻击的形式。
函数反编译
大多数javaScript实现,还提供一种将以求值过的javaScript代码进行‘反编译’的功能。
我们称这一过程为序列化,下面将函数反编译成字符串,
反编译听起来很复杂,其实很简单,是由函数的toString()方法来执行,让我们用下面的函数测试一下,
需要注意的是,toString()的返回值包含原始声明的所有空格,包括行结束符。
反编译行为有很多潜在的用途,尤其是在宏指令和代码重写的时候,在prototype javaScript库中,有一个比较有趣的应用是,将函数进行反编译从而读取该函数的参数,然后将这些参数名称保存到一个数组中,这是非常有用的,通常用于确定函数想得到什么样的参数。如下代码是Prototype中推断函数参数名称的一段简化代码。
有的浏览器不支持反编译功能,我们需要使用特性仿真来测试浏览器是否支持反编译功能,其中一种方式如下:
再一次使用了正则表达式,向test方法传入一个函数,并将结果存储在一个变量中,以供稍后进行使用,截至目前,我们讨论了运行时代码求值的各种方式。现在,让我们把这些知识转化为行动。
代码求值实战
JSON转化
在早些浏览器不提供parse()和stringify()方法,我们自己实现一个,将json字符串转化为javaScript对象
非常简单,在大多数javaScript引擎中都表现良好。
但使用eval()做json解析时需要注意的主要是:通常,JSON数据来自于远程服务器,盲目执行远程服务器上的不可信代码,基本是不可取的。
导入有命名空间的代码
如果要将命名空间化的代码引入当前上下文,那又该如何做呢?这个问题很有挑战性,考虑到javaScript语言中没有简单或支持的方法,大多数时候,我们不得不使用类似如下的代码:
对于将命名空间导入到当前上下文,base2库提供了一个非常有趣的方案,因为没有办法将该问题进行自动化操作,因此我们可以利用运行时求值让该实现变得更简单。
面向切面的脚本标签
AOP,或面向方面编程,维基百科将其定义为“一种旨在通过分离横切关注点而增加模块化的编程范式”是的,听起来让我们头晕。
让我们来看一下如何充分利用AOP的思想。
我们之前已经讨论过,在页面的脚本标记中使用无效的类型属性,包含一些不想让浏览器触碰的新数据,我们可以把这个概念更进一步具体化,使用他增强现有的javaScript。
在本例中,我们提供了一个浏览器忽略执行的自定义脚本块,在页面的onload处理程序中,查询所有的脚本块,在筛选自定义类型的脚本块,最后用本章前面开发的globalEval()函数,在全局作用域内对脚本块的内容进行求值。
这是一个简单的例子,但这种技术有更复杂且更有意义的用途,例如,将自定义脚本块和jquery.tmpl()方法一块使用用于提供运行时模版,利用它可以在用户界面上执行脚本,或者在准备操作DOM的时候,甚至是相邻元素上执行脚本。这种技术的应用程序仅限于页面开发人员的想象。
元语言和领域特定语言
关于运行时代码求值的一个最重要示例,可以在构建于javaScript之上的其他编程语言实现中看到:元语言。如果你愿意,可以将其动态转换成javaScript源代码并求值,通常,这种定制语言非常特定于开发人员的业务需求,并且已经创造了领域特定语言(DSL)这样的名字。
有两个DSL,特别有趣,processing.js和Objective-J,有兴趣的可以了解一下。