万恶的 eval() ?

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 万恶的 eval() ?

什么是eval()?

eval()函数就是一个完整的 ECMAScript 解释器,它接收一个参数,即一个要执行的 ECMAScript(JavaScript)字符串。


与很多解释型语言一样,JavaScript有能力解释JavaScript源代码字符串,对它们求值以产生一个值。JavaScript就是通过全局函数eval()来对源代码字符串求值的。


关于eval()是函数还是操作符?

eval()是一个函数,其实应该是个操作符。JavaScript语言最初的版本定义了一个eval()函数,语言设计者和解释器开发者一直对它加以限制,导致它越来越像操作符。


现代JavaScript解释器会执行大量代码分析和优化。一般来说,如果一个函数调用eval(),则解释器将无法再优化该函数。


把eval()定义为函数的问题在于可以给它起不同的名字如果可以这样,那么解释器无法确定哪个函数会调用eval(),也就无法激进优化。假如eval()是个操作符(即保留字),那这个问题就可以避免。


作用域问题

通过 eval()执行的代码属于该调用所在上下文,被执行的代码与该上下文拥有相同的作用域链。


在执行 eval(..) 之后的代码时,引擎并不“知道”或“在意”前面的代码是以动态形式插入进来,并对词法作用域的环境进行修改的。引擎只会如往常地进行词法作用域查找。


eval()的使用

关于参数

eval()期待一个参数。

1.如果给它传入任何非字符串值,它会简单地返回这个值。

2.如果传入字符串,它会尝试把这个字符串当成JavaScript代码来解析,

①解析失败会抛出SyntaxError。

②如果最后一个表达式或语句没有值则返回undefined。

③如果解析字符串成功,它会求值代码并返回该字符串中最后一个表达式或语句的值。

④如果求值字符串抛出异常,该异常会从调用eval()的地方传播出来。


直接eval()

对于eval()(在像这样调用时),关键在于它会使用调用它的代码的变量环境。它会像本地代码一样查找变量的值、定义新变量和函数。

  1. 定义在包含上下文中的变量可以在 eval()调用内部被引用

如果一个函数定义了一个局部变量x,然后调用了eval("x"),那它会取得这个局部变量的值。

function foo() {
    let x = 1 + 1;
    eval("console.log(x)"); // 2
}
foo()
复制代码


  1. 声明局部变量

如果这个函数调用了eval("var y = 3;"),则会声明一个新局部变量y。

eval("var y = '3';"); 
console.log(y); // 3
复制代码


eval()声明一个局部函数

eval(`function foo() {
    let x = 1 + 1;
    console.log(x);
}`)
foo()
复制代码


  1. 如果被求值的字符串使用了letconst,则声明的变量或常量会被限制在求值的局部作用域内,不会定义到调用环境中。
    通过 eval()定义的任何变量和函数都不会被提升,这是因为在解析代码的时候,它们是被包含在一个字符串中的。它们只是在 eval()执行的时候才会被创建。
eval("var msg = 'hello world';"); 
console.log(msg); // hello world
eval("let msg = 'hello world';"); 
console.log(msg); // Reference Error: msg is not defined
复制代码


  1. 传给eval()的代码字符串本身必须从语法上说得通:不能使用它向函数中粘贴代码片段。

eval("return;")是没有意义的,因为return只在函数中是合法的,即使被求值的字符串使用与调用函数相同的变量环境,这个字符串也不会成为函数的一部分。

只要这个字符串本身可以作为独立的脚本运行(即使像x=0这么短),都可以合法地传给eval()。否则,eval()将抛出SyntaxError。


严格模式

一、严格模式对eval()函数增加了更多限制,甚至对标识符“eval”的使用也进行了限制。


当我们在严格模式下调用eval()时,或者当被求值的代码字符串以“use strict”指令开头时,eval(..) 在运行时有其自己的词法作用域,eval()会基于一个私有变量环境进行局部求值。这意味着在严格模式下,被求值的代码可以查询和设置局部变量,但不能在局部作用域中定义新变量或函数也就是说在 eval()内部创建的变量和函数无法被外部访问。


function foo(str) {
    "use strict";
    eval(str);
    console.log(a); // ReferenceError: a is not defined
}
foo("var a = 2");
复制代码


二、严格模式让eval()变得更像操作符,因为“eval”在严格模式下会变成保留字。

不能再使用新值来重写eval()函数。通过名字“eval”来声明变量、函数、函数参数或捕获块参数都是不允许的。


全局eval()

一、

eval()会干扰JavaScript的优化程序,是因为它能够修改局部变量。解释器也不会过多优化调用eval()的函数。

JavaScript规范中说,如果eval()被以“eval”之外的其他名字调用时,它应该把字符串当成顶级全局代码来求值。

被求值的代码可能定义新全局变量或全局函数,可能修改全局变量,但它不会再使用或修改调用函数的局部变量。因此也就不会妨碍局部优化。

使用名字“eval”来调用eval()函数就叫作“直接eval”(这样就有点保留字的感觉了)。直接调用eval()使用的是调用上下文的变量环境。如果在顶级代码中调用eval(),则它操作的一定是全局变量和全局函数。


二、

任何其他调用方式,包括间接调用,都使用全局对象作为变量环境,因而不能读、写或定义局部变量或函数(无论直接调用还是间接调用都只能通过var来定义新变量。在被求值的字符串中使用let和const创建的变量和常量会被限定在求值的局部作用域内,不会修改调用或全局环境)。

let x = 'g';
let y = 'g';
let gEval = eval;
function g() {
    let x = 'l';
    gEval("x += 'changed'");
    return x;
}
function l() {
    let y = 'l';
    eval("y += 'changed'");
    return y;
}
console.log(g() , x); // l gchanged  g函数外部x改变了
console.log(l() , y); // lchanged g  l函数内部y改变了
复制代码


这种全局求值的能力不仅仅是为了适应优化程序的需求,同时也是一种极其有用的特性,可以让我们把代码字符串作为独立、顶级的脚本来执行。假如你必须使用eval(),那很可能应该使用它的全局求值而不是局部求值。


为什么不提倡使用eval()?

解释代码字符串的能力是非常强大的,但也非常危险。在使用 eval()的时候必须极为慎重,特别是在解释用户输入的内容时。因为这个方法会对 XSS 利用暴露出很大的攻击面。恶意用户可能插入会导致你网站或应用崩溃的代码。


某些Web服务器使用HTTP的"Content-Security-Policy"头部对整个网站禁用eval()

无论在什么时候eval()都可以运行期间修改书写期的词法作用域。除了严格模式。


和eavl函数有类似作用

setTimeout(..) 和 setInterval(..) 的第一个参数可以是字符串,字符串的内容可以被解释为一段动态生成的函数代码。这些功能已经过时且并不被提倡。不要使用它们!


new Function(..) 函数的行为也很类似,最后一个参数可以接受代码字符串,并将其转化为动态生成的函数(前面的参数是这个新生成的函数的形参)。这种构建函数的语法比 eval(..) 略微安全一些,但也要尽量避免使用。


在程序中动态生成代码的使用场景非常罕见,因为它所带来的好处无法抵消性能上的损失。



目录
相关文章
|
6月前
|
安全 Python
使用eval函数需要注意哪些方面
使用eval函数需要注意哪些方面
43 0
|
前端开发
前端学习笔记202306学习笔记第三十七天-with语句和eval函数1
前端学习笔记202306学习笔记第三十七天-with语句和eval函数1
57 0
|
前端开发
前端学习笔记202306学习笔记第三十七天-with语句和eval函数2
前端学习笔记202306学习笔记第三十七天-with语句和eval函数2
49 0
|
小程序 数据安全/隐私保护 Python
Python print 函数 \r 转义字符的玩法及解析——转圈效果、动态表情、小数点加载、进度条
Python print 函数 \r 转义字符的玩法及解析——转圈效果、动态表情、小数点加载、进度条
436 0
|
JavaScript 前端开发
JavaScript的for循环语句练习之解决小马过河的问题
JavaScript的for循环语句练习之解决小马过河的问题 上篇文章我们说了怎么计算数学问题鸡兔同笼,这边文章咱们来说一下稍微再加大写难度的问题,小马过河。问题是现在有一群马,大马可以驮2石粮食,中马可以驮1石粮食,两头小马才可以驮1石粮食,现在要用100匹马,驮100石粮食,该如何调配? 在这里插入图片描述 1.解决这个问题主要是要先确定三种马加起来一共有100只,同时驮的粮食加起来也得是100。 // 假设大马为x,中马为y,小马为z 那现在便可得出函数 x+y+z=100 2x+y+z÷2=100 // 转换为for循环解决则变成了: // 假设
JavaScript的for循环语句练习之解决小马过河的问题
福利来了:vaw-layouts,来不及解释了,赶紧上车
继vue-admin-work开源框架开发完之后,请多小伙伴问我,要怎么样快速搭建公司的后台管理系统。目前的解决方案是在 vue-admin-work的基础上进行删除无用的代码或者修改自己的需求。当了解了这一需求之后,我们想了一下也确实不方便,所以为了解决这一痛点 vaw-layouts项目就正式诞生了。
福利来了:vaw-layouts,来不及解释了,赶紧上车
xprintlog:给print函数加点料
xprintlog:给print函数加点料
94 0
|
Python
更好的print :嫌弃print太单调 那么来试试这几种方法吧
更好的print :嫌弃print太单调 那么来试试这几种方法吧
更好的print :嫌弃print太单调 那么来试试这几种方法吧
|
JavaScript 前端开发
JS查漏补缺——with语句、eval函数
JS查漏补缺系列是我在学习JS高级语法时做的笔记,通过实践费曼学习法进一步加深自己对其的理解,也希望别人能通过我的笔记能学习到相关的知识点。这一次我们来了解with语句、eval函数
94 0
|
安全 前端开发 测试技术
经理看到我用eval,过来就是一jio
背景: 前端根据用户输入的内容(输入一个数据),视图层自动给解析并求值成 字符串/整形/字典/浮点型/列表等。
经理看到我用eval,过来就是一jio