aboutsummaryrefslogtreecommitdiff
path: root/files/zh-cn/web/javascript/reference/global_objects/eval/index.html
blob: 9e2ecdfcef41a3a46ae43ab36760e653b2fd3ab5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
---
title: eval()
slug: Web/JavaScript/Reference/Global_Objects/eval
tags:
  - JavaScript
  - eval
  - 参考
translation_of: Web/JavaScript/Reference/Global_Objects/eval
---
<div>{{jsSidebar("Objects")}}</div>

<p><code><strong>eval()</strong></code><strong> </strong>函数会将传入的字符串当做 JavaScript 代码进行执行。</p>

<div>{{EmbedInteractiveExample("pages/js/globalprops-eval.html")}}</div>

<h2 id="语法">语法</h2>

<pre class="syntaxbox"><code>eval(<em>string</em>)</code></pre>

<h3 id="参数">参数</h3>

<dl>
 <dt><code>string</code></dt>
 <dd>一个表示 JavaScript 表达式、语句或一系列语句的字符串。表达式可以包含变量与已存在对象的属性。</dd>
</dl>

<h3 id="返回值">返回值</h3>

<p>返回字符串中代码的返回值。如果返回值为空,则返回 {{jsxref("undefined")}}</p>

<h2 id="描述">描述</h2>

<p><code>eval()</code> 是全局对象的一个函数属性。</p>

<p><code>eval()</code> 的参数是一个字符串。如果字符串表示的是表达式,<code>eval()</code> 会对表达式进行求值。如果参数表示一个或多个 JavaScript 语句,那么<code>eval()</code> 就会执行这些语句。不需要用 <code>eval()</code> 来执行一个算术表达式:因为 JavaScript 可以自动为算术表达式求值。</p>

<p>如果你以字符串的形式构造了算术表达式,那么可以在后面用 <code>eval()</code>对它求值。例如,假设你有一个变量 <code>x</code>,您可以通过将表达式的字符串值(例如 <code>3 * x + 2</code>)赋值给一个变量,然后在你的代码后面的其他地方调用 <code>eval()</code>,来推迟涉及 <code>x</code> 的表达式的求值。</p>

<p>如果 <code>eval()</code> 的参数不是字符串, <code>eval()</code> 会将参数原封不动地返回。在下面的例子中,<code>String</code> 构造器被指定,而 <code>eval()</code> 返回了 <code>String</code> 对象而不是执行字符串。</p>

<pre class="brush: js">eval(new String("2 + 2")); // 返回了包含"2 + 2"的字符串对象
eval("2 + 2");             // returns 4
</pre>

<p>你可以使用一些通用的方法来绕过这个限制,例如使用 <code>toString()</code></p>

<pre class="brush: js">var expression = new String("2 + 2");
eval(expression.toString());
</pre>

<p>如果你间接的使用 <code>eval()</code>,比如通过一个引用来调用它,而不是直接的调用 <code>eval</code>。 从 <a href="http://www.ecma-international.org/ecma-262/5.1/#sec-10.4.2">ECMAScript 5</a> 起,它工作在全局作用域下,而不是局部作用域中。这就意味着,例如,下面的代码的作用声明创建一个全局函数,并且 <code>eval</code> 中的这些代码在执行期间不能在被调用的作用域中访问局部变量。</p>

<pre class="brush: js">function test() {
  var x = 2, y = 4;
  console.log(eval('x + y'));  // 直接调用,使用本地作用域,结果是 6
  var geval = eval; // 等价于在全局作用域调用
  console.log(geval('x + y')); // 间接调用,使用全局作用域,throws ReferenceError 因为`x`未定义
<code>  (0, eval)('x + y'); // 另一个间接调用的例子
​</code>}</pre>

<h2 id="Don.27t_use_eval.21" name="Don.27t_use_eval.21">永远不要使用 <code>eval</code></h2>

<p><code>eval()</code> 是一个危险的函数, 它使用与调用者相同的权限执行代码。如果你用 <code>eval()</code> 运行的字符串代码被恶意方(不怀好意的人)修改,您最终可能会在您的网页/扩展程序的权限下,在用户计算机上运行恶意代码。更重要的是,第三方代码可以看到某一个 <code>eval()</code> 被调用时的作用域,这也有可能导致一些不同方式的攻击。相似的 {{jsxref("Global_Objects/Function", "Function")}} 就不容易被攻击。</p>

<p><code>eval()</code> 通常比其他替代方法更慢,因为它必须调用 JS 解释器,而许多其他结构则可被现代 JS 引擎进行优化。</p>

<p>此外,现代JavaScript解释器将javascript转换为机器代码。 这意味着任何变量命名的概念都会被删除。 因此,任意一个eval的使用都会强制浏览器进行冗长的变量名称查找,以确定变量在机器代码中的位置并设置其值。 另外,新内容将会通过 <code>eval()</code> 引进给变量, 比如更改该变量的类型,因此会强制浏览器重新执行所有已经生成的机器代码以进行补偿。 但是,(谢天谢地)存在一个非常好的eval替代方法:只需使用 <a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function">window.Function</a>。 这有个例子方便你了解如何将<code>eval()</code>的使用转变为<code>Function()</code></p>

<p>使用eval的糟糕代码:</p>

<pre class="brush:js example-bad">function looseJsonParse(obj){
    return eval("(" + obj + ")");
}
console.log(looseJsonParse(
   "{a:(4-1), b:function(){}, c:new Date()}"
))
</pre>

<p> 不用eval的更好的代码:</p>

<pre class="brush:js example-good">function looseJsonParse(obj){
    return Function('"use strict";return (' + obj + ')')();
}
console.log(looseJsonParse(
   "{a:(4-1), b:function(){}, c:new Date()}"
))

</pre>

<p>比较上面的两个代码片段,两个代码片段似乎是以相同的方式工作,但再想一想:eval的这个代码的速度要慢得多。 注意<code>c: new Date()</code>在执行体中。 在没有eval的函数中,对象在全局范围内被用来进行计算,因此浏览器可以放心的假设<code>Date</code>是来自<code>window.Date</code>的而不是一个名为<code>Date</code>的局部变量。 然而,在使用<code>eval()</code>的代码中,浏览器不能假设这一点,因为如果您的代码是下面这个:</p>

<pre class="brush:js example-bad">function Date(n){
    return ["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"][n%7 || 0];
}
function looseJsonParse(obj){
    return eval("(" + obj + ")");
}
console.log(looseJsonParse(
   "{a:(4-1), b:function(){}, c:new Date()}"
))
</pre>

<p>因此,在<code>eval()</code>版本的代码中,浏览器被迫进行高代价的查找调用以检查是否存在名为Date()的任何局部变量。 与<code>Function()</code>相比,这是非常低效的。</p>

<p>在类似的情况下,如果您确实希望能够从<code>Function()</code>内部的代码调用<code>Date</code>函数,该怎么办? 你应该躲避并退回到<code>eval()</code>吗? 绝对不是,永远不要这么做。 而是尝试下面的方法。</p>

<pre class="brush:js example-good">function Date(n){
    return ["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"][n%7 || 0];
}
function runCodeWithDateFunction(obj){
    return Function('"use strict";return (' + obj + ')')()(
        Date
    );
}
console.log(runCodeWithDateFunction(
   "function(Date){ return Date(5) }"
))
</pre>

<p>由于三重嵌套函数,上面的代码似乎效率低下,但让我们分析一下上述有效方法的好处:</p>

<p>1.它使得传递给<code>runCodeWithDateFunction</code>的字符串中的代码更少。</p>

<p>2.函数调用开销很小,使得代码尺寸小得多,值得获益</p>

<p>3. <code>Function()</code>更容易让你的代码利用特性修饰<code>"use strict"</code>;</p>

<p>4.代码不使用eval(),使其比其他方式快几个数量级。</p>

<p>最后,我们来看看简化版。 使用如上所示的<code>Function()</code>,您可以更有效地缩小传递给<code>runCodeWithDateFunction</code>的代码字符串,因为函数参数名称也可以缩小,如下面的缩小代码所示。</p>

<pre class="brush:js">console.log(Function('"use strict";return(function(a){return a(5)})')()(function(a){
return"Monday Tuesday Wednesday Thursday Friday Saturday Sunday".split(" ")[a%7||0]}));</pre>

<p>对于常见用例,<code>eval()</code><code>Function()</code>还有更安全(而且更快!)的替代方案。</p>

<h3 id="访问成员属性">访问成员属性</h3>

<p>你不应该去使用 <code>eval()</code> 来将属性名字转化为属性。考虑下面的这个例子,被访问对象的属性在它被执行之前都会未知的。这里可以用 eval 处理:</p>

<pre class="brush: js">var obj = { a: 20, b: 30 };
var propName = getPropName(); // 返回 "a" 或 "b"

eval( 'var result = obj.' + propsName )</pre>

<p>但是,这里并不是必须得使用 <code>eval()</code> 。事实上,这里并不建议这样使用。可以使用 <a href="/zh-CN/docs/Web/JavaScript/Reference/Operators/Property_Accessors">属性访问器</a> 进行代替,它更快、更安全:</p>

<pre class="brush: js">var obj = { a: 20, b: 30 }
var propName = getPropName(); // 返回 "a" 或 "b"
var result = obj[ propName ]; // obj[ "a" ] 与 obj.a 等价</pre>

<p>你还可以使用这个方法去访问子代的属性。如下:</p>

<pre class="brush: js">var obj = {a: {b: {c: 0}}};
var propPath = getPropPath(); // 例如返回 "a.b.c"

eval( 'var result = obj.' + propPath )
</pre>

<p>这里,可以通过分割属性路径、循环遍历不同的属性,来避免 <code>eval()</code></p>

<pre class="brush:js">function getDescendantProp(obj, desc) {
  var arr = desc.split('.');
  while (arr.length) {
    obj = obj[arr.shift()];
  }
  return obj;
}

var obj = {a: {b: {c: 0}}};
var propPath = getPropPath(); // 例如返回 "a.b.c"
var result = getDescendantProp(obj, propPath);</pre>

<p>同样的方法也可实现设置子代的属性值:</p>

<pre class="brush:js">function setDescendantProp(obj, desc, value) {
  var arr = desc.split('.');
  while (arr.length &gt; 1) {
    obj = obj[arr.shift()];
  }
  return obj[arr[0]] = value;
}

var obj = {a: {b: {c: 0}}};
var propPath = getPropPath();  // 例如,返回 "a.b.c"
var result = setDescendantProp(obj, propPath, 1);  // a.b.c 值为 1</pre>

<h3 id="使用函数而非代码段">使用函数而非代码段</h3>

<p>JavaScript 拥有 <a href="/zh-CN/docs/Glossary/First-class_Function">first-class functions</a>,这意味着你可以将函数直接作为参数传递给其他接口,将他们保存在变量中或者对象的属性中,等等。很多DOM的API都用这种思路进行设计,你也可以(或者应该)这样子设计你的代码:</p>

<pre class="brush: js">// 代替 setTimeout(" ... ", 1000) 写法:
setTimeout(function() { ... }, 1000);

// 代替 elt.setAttribute("onclick", "...") 写法:
elt.addEventListener('click', function() { ... } , false);</pre>

<p><a href="/zh-CN/docs/Web/JavaScript/Closures">闭包</a> 也有助于创建参数化函数而不用连接字符串。</p>

<h3 id="解析_JSON(将字符串转化为_JavaScript_对象)">解析 JSON(将字符串转化为 JavaScript 对象)</h3>

<p>如果你在调用 <code>eval()</code> 传入的字符串参数中包含数据(如:一个数组“[1,2,3]”)而不是代码,你应该考虑将其转换为 <a href="/zh-CN/docs/Glossary/JSON">JSON</a> 对象,这允许你用JavaScript语法的子集来表示数据。<a href="/zh-CN/docs/Downloading_JSON_and_JavaScript_in_extensions">在扩展中下载JSON和JavaScript</a></p>

<p>提示:因为 JSON 语法子集相对于 JavaScript 语法子集比较有局限性,很多在 JavaScript 中可用的特性在 JSON 中就不起作用了。比如,后缀逗号在 JSON 中不支持,并且对象中的属性名在 JSON 中必须用引号括起来。请务必使用 JSON 序列化方法来生成稍后将被解析为 JSON 的字符串。</p>

<h3 id="尽量传递数据而非代码">尽量传递数据而非代码</h3>

<p>例如,设计为抓取网页内容的扩展,可能会在XPath中定义抓取规则,而不是在 JavaScript 代码中。</p>

<h3 id="以有限权限运行代码">以有限权限运行代码</h3>

<p>如果你必须执行这段代码, 应考虑以更低的权限运行。此建议主要适用于扩展和 XUL 应用程序,可以使用 <a href="/zh-CN/docs/Components.utils.evalInSandbox">Components.utils.evalInSandbox</a> 做到降低权限。</p>

<h2 id="例子">例子</h2>

<h3 id="使用_eval">使用 <code>eval</code></h3>

<p>在下面的代码中,两种包含了 <code>eval()</code> 的声明都返回了 42。第一种是对字符串 "<code>x + y + 1</code>" 求值;第二种是对字符串 "<code>42</code>" 求值。</p>

<pre class="brush:js">var x = 2;
var y = 39;
var z = "42";
eval("x + y + 1"); // returns 42
eval(z);           // returns 42
</pre>

<h3 id="使用_eval_执行一串_JavaScript_语句">使用 <code>eval</code> 执行一串 JavaScript 语句</h3>

<p>下面的例子使用 <code>eval()</code> 来执行 <code>str</code> 字符串。这个字符串包含了如果 <code>x</code> 等于5,就打开一个Alert 对话框并对 <code>z</code> 赋值 42,否则就对 <code>z</code> 赋值 0 的 JavaScript 语句。 当第二个声明被执行,<code>eval()</code> 将会令字符串被执行,并最终返回赋值给 <code>z</code> 的 42。</p>

<pre class="brush:js">var x = 5;
var str = "if (x == 5) {console.log('z is 42'); z = 42;} else z = 0;";

console.log('z is ', eval(str));</pre>

<p>如果您定义了多个值,则会返回最后一个值。</p>

<pre class="brush:js">var x = 5;
var str = "if (x == 5) {console.log('z is 42'); z = 42; x = 420; } else z = 0;";

console.log('x is ', eval(str)); // z is 42  x is 420
</pre>

<h3 id="返回值_2">返回值</h3>

<p><code>eval</code> 返回最后一个表达式的值。</p>

<pre class="brush:js">var str = 'if ( a ) { 1 + 1; } else { 1 + 2; }';
var a = true;
var b = eval(str);  // returns 2

console.log('b is : ' + b);

a = false;
b = eval(str);  // returns 3

console.log('b is : ' + b);</pre>

<h3 id="eval_中函数作为字符串被定义需要“(”和“)”作为前缀和后缀"><code>eval</code> 中函数作为字符串被定义需要“(”和“)”作为前缀和后缀</h3>

<pre class="brush:js">var fctStr1 = 'function a() {}'
var fctStr2 = '(function a() {})'
var fct1 = eval(fctStr1)  // 返回undefined
var fct2 = eval(fctStr2)  // 返回一个函数
</pre>

<h2 id="规范">规范</h2>

<table class="standard-table">
 <tbody>
  <tr>
   <th scope="col">Specification</th>
   <th scope="col">Status</th>
   <th scope="col">Comment</th>
  </tr>
  <tr>
   <td>{{SpecName('ES1')}}</td>
   <td>{{Spec2('ES1')}}</td>
   <td>Initial definition.</td>
  </tr>
  <tr>
   <td>{{SpecName('ES5.1', '#sec-15.1.2.1', 'eval')}}</td>
   <td>{{Spec2('ES5.1')}}</td>
   <td></td>
  </tr>
  <tr>
   <td>{{SpecName('ES6', '#sec-eval-x', 'eval')}}</td>
   <td>{{Spec2('ES6')}}</td>
   <td></td>
  </tr>
  <tr>
   <td>{{SpecName('ESDraft', '#sec-eval-x', 'eval')}}</td>
   <td>{{Spec2('ESDraft')}}</td>
   <td></td>
  </tr>
 </tbody>
</table>

<h2 id="浏览器兼容性">浏览器兼容性</h2>



<p>{{Compat("javascript.builtins.eval")}}</p>

<h2 id="Firefox_相关">Firefox 相关</h2>

<ul>
 <li>从历史上看,<code>eval()</code> 有一个可选的第二个参数,指定上下文执行对象。 这个参数是非标准的,并且明确地从 Firefox 4 中删除。请参阅 {{bug(531675)}}</li>
</ul>

<h2 id="参见">参见</h2>

<ul>
 <li>{{jsxref("Global_Objects/uneval", "uneval()")}}</li>
 <li><a href="/zh-CN/docs/Web/JavaScript/Reference/Operators/Property_Accessors">Property accessors</a></li>
 <li><a href="/zh-CN/Add-ons/WebExtensions/Content_scripts#Using_eval()_in_content_scripts">WebExtensions: Using eval in content scripts</a></li>
</ul>