with

js 的 with 是为对象访问提供命名空间式的访问方式,with 创建一个对象的命名空间,在这个命名空间内你可以直接访问对象的属性,而不需要通过对象来访问:

  1. const o = { a: 1, b: 2 };
  2. with (o) {
  3. console.log(a); // 1
  4. b = 3; // o: { a: 1, b: 3 }
  5. }
  • 数据泄露

他还有作用域链的性质。如果给不存在的属性赋值,将会沿着作用域链给该变量赋值,在没有找到变量时,非严格模式下将会自动在全局创建一个变量。这就导致了数据泄露。

  1. const o = { a: 1, b: 2 };
  2. const p = { a: 3 };
  3. with (o) {
  4. console.log(a); // 1
  5. with (p) {
  6. console.log(a); // 3
  7. b = 4; // o: { a: 1, b: 4 }
  8. c = 5; // window.c = 5
  9. }
  10. }
  • 性能下降

js 预编译阶段会进行的优化,由于 with 创建新的词法作用域,导致 o 的 a 属性和 o 分离开位于两个不同的作用域,不能快速找到标识符,引擎将不会做任何优化。

  1. function f () {
  2. const o = { a: 1 }
  3. console.time();
  4. with(o) {
  5. for (let i = 0; i < 100000; i++) {
  6. a = 2;
  7. }
  8. }
  9. console.timeEnd();
  10. }
  • 语义不明
    1. function f(x, o) {
    2. with (o)
    3. print(x);
    4. }
    f被调用时,x有可能能取到值,也可能是undefined,如果能取到, 有可能是在o上取的值,也可能是函数的第一个参数x的值(如果o中没有这个属性的话)

eval

eval()函数会将传入的字符串当做 JavaScript 代码进行执行。

  1. eval(string)
  • 参数string

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

  • 返回值

返回字符串中代码的返回值。如果返回值为空,则返回 undefined

  1. console.log(eval('2 + 2'));
  2. // expected output: 4

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

  1. eval(new String("2 + 2")); // 返回了包含"2 + 2"的字符串对象
  2. eval("2 + 2"); // returns 4

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

间接调用时,eval 运行于全局作用域。

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

间接调用时,eval 并不会修改调用函数作用域内的任何东西。

JS 解释器有 fast path 和 slow path 两种模式,当直接调用 eval 时,解释器处于 slow path。因为此时作用域是不可控的,需要监听整个作用域,不能应用 v8 的一些编译优化,相应的编译效率也会比 fast path 低。

大家抵制 eval 的原因主要是以下几个原因:

  1. 降低性能。具体原因上文已经提到了,网上一些文章甚至说 eval() 会拖慢性能 10 倍。
  2. 安全问题。因为它的动态执行特性,给被求值的字符串赋予了太大的权力,于是大家担心可能因此导致 XSS 等攻击。
  3. 调试困难。eval 就像一个黑盒,其执行的代码很难进行断点调试。

Function

Function 构造函数创建一个新的 Function 对象。直接调用此构造函数可用动态创建函数,但会遇到和 eval 类似的的安全问题和(相对较小的)性能问题。然而,与 eval 不同的是,Function 创建的函数只能在全局作用域中运行。

  1. const sum = new Function('a', 'b', 'return a + b');
  2. console.log(sum(2, 6));
  3. // expected output: 8
  • 语法

    1. new Function ([arg1[, arg2[, ...argN]],] functionBody)
  • arg1, arg2, ... argN

被函数使用的参数的名称必须是合法命名的。参数名称是一个有效的JavaScript标识符的字符串,或者一个用逗号分隔的有效字符串的列表;例如“×”,“theValue”,或“a,b”。

  • functionBody

一个含有包括函数定义的 JavaScript 语句的字符串。

由 Function 构造器创建的函数不会创建当前环境的闭包,它们总是被创建于全局环境,因此在运行时它们只能访问全局变量和自己的局部变量。

不能访问它们被 Function 构造器创建时所在的作用域的变量。这一点与使用 eval 执行创建函数的代码不同。

  1. var x = 10;
  2. function createFunction1() {
  3. var x = 20;
  4. return new Function('return x;'); // 这里的 x 指向最上面全局作用域内的 x
  5. }