Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(0, eval)('xxx') 和 eval('xxx') 这两种写法有什么区别? #2

Open
csu-feizao opened this issue Mar 8, 2023 · 0 comments
Open
Labels

Comments

@csu-feizao
Copy link
Owner

最近在看 SystemJS 源码时,发现 SystemJS 执行 script 时使用的是 (0, eval)(scriptText),特别好奇为啥这样写。经过了解,发现这种写法十分巧妙,故留下一篇记录

SystemJS 源码:

var instantiate = systemJSPrototype.instantiate;
var jsContentTypeRegEx = /^(text|application)\/(x-)?javascript(;|$)/;
systemJSPrototype.instantiate = function (url, parent, meta) {
  var loader = this;
  if (!this.shouldFetch(url, parent, meta))
    return instantiate.apply(this, arguments);
  return this.fetch(url, {
    credentials: 'same-origin',
    integrity: importMap.integrity[url],
    meta
  })
  .then(function (res) {
    if (!res.ok)
      throw Error(errMsg(7, process.env.SYSTEM_PRODUCTION ? [res.status, res.statusText, url, parent].join(', ') : res.status + ' ' + res.statusText + ', loading ' + url + (parent ? ' from ' + parent : '')));
    var contentType = res.headers.get('content-type');
    if (!contentType || !jsContentTypeRegEx.test(contentType))
      throw Error(errMsg(4, process.env.SYSTEM_PRODUCTION ? contentType : 'Unknown Content-Type "' + contentType + '", loading ' + url + (parent ? ' from ' + parent : '')));
    return res.text().then(function (source) {
      if (source.indexOf('//# sourceURL=') < 0)
        source += '\n//# sourceURL=' + url;
      // 这里使用了 `(0, eval)(scriptText)` 来执行代码
      (0, eval)(source);
      return loader.getRegister(url);
    });
  });
};

逗号运算符

上面代码中的 (0, eval) 是用了逗号运算符语法,不管括号中有多少个表达式,总是返回最后一个表达式的值。最常见的使用场景是在函数返回值前处理一些操作,如

function myFunc() {
  let x = 0;

  return (x += 1, x); // 与 return ++x; 等价
}

eval

根据逗号运算符的特点,表达式 (0, eval) 返回的是 eval 函数本身。

const eval1 = (0, eval)
console.log(eval1 === eval) // true

那么 (0, eval)('xxx')eval('xxx') 是不是就没有任何区别了吗?

实则不然,根据 Ecma 规范,eval 有两种模式,直接调用间接调用。直接调用是在当前作用域,间接调用是在全局作用域。通过逗号运算符表达式返回 eval 的引用能让 eval 变成间接调用。试着执行下面代码

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

结论

eval 有两种模式,直接调用间接调用。直接调用是在当前作用域,间接调用是在全局作用域。直接调用会让 eval 中的代码能够获取到当前作用域链上的全部变量,就可能导致局部变量覆盖了全局变量,甚至可能作用域链上的任意变量值都会被执行的代码所影响。而通过逗号运算符表达式返回 eval 的引用能让 eval 变成间接调用,因此能保证 eval 中的代码不会和当前作用域链的变量产生相互影响。

参考资料

eval - MDN
逗号运算符 - MDN
Ecma eval 规范

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant