为了账号安全,请及时绑定邮箱和手机立即绑定

在标准的JS里 eval和window.eval属不同的语法

标签:
JavaScript

  最近写的一个脚本里需要在全局下执行代码。这个例子以前见到过,在IE6,7,8下用window.exeScript方法,其他则调用window.eval方法即可。

   

  看似很简单,运行也正确。eval声明的a只有内部可见;window.eval声明的b全局可见。 

<script>
function A()
{
    eval("var a=1");
    window.eval("var b=1");
}

A();

alert(typeof a);    //undefined
alert(typeof b);    //number
</script>

  不过事后回想起来感觉有些诡异。eval和window.eval不就同个函数吗,为什么加上window.意义就不同了呢?如果说eval内部判断了当前this,那么eval和window.eval执行时,this都是指向window。

   

  如果用变量p=window.eval,那么p()和window.eval()还一样吗?

<script>
function A()
{
    var p = window.eval;

    alert(p === window.eval);    //true
    alert(p === eval);           //true

    eval("var a=1");
    p("var b=1");
}

A();

alert(typeof a);    //undefined
alert(typeof b);    //number
</script>

   

  经测试,p()和window.eval()的效果完全一样,都是在全局执行。并且p === window.eval 和 p === eval 同时成立!这显然很奇怪,不过还有更奇怪的事在后面!如果我们用q=eval,结果完全出人意料:

<script>
function A()
{
    var p = window.eval;
    var q = eval;

    p("var a=1");
    q("var b=1");
}

A();

alert(typeof a);    //number
alert(typeof b);    //number
</script>

  居然显示的都是number!也就是说指向eval的p,实际效果却是window.eval。他们都是在全局执行代码!

  为了验证是否是引用上的区别,我们再做一次测试: 

<script>
function A()
{
    var q = eval;

    q("var b=1");
    eval("var a=1");
}

A();

alert(typeof a);    //undefined
alert(typeof b);    //number
</script>

  明显,eval执行的a被留在了内部,而q=eval的b却是全局的!

  

  于是我推测,标准JavaScript下的eval,也许和this一样,既是关键字,也是一个变量(函数变量)。如果是当关键字调用的话,即字面上的eval(),那么在当前的上下文里执行;否则,即通过变量引用调用的话,就在全局上执行。这样就可以解释 window.eval 和 eval 的区别了:window.eval仅仅是window对象里的一个叫eval的属性,一个指向eval函数的属性。和window.eval2,window.eval3一样,仅仅一个属性,并非字面上的eval。

  

  为了验证这个猜测,我查看了 FireFox 的脚本引擎源码。其中eval真正的执行部分定义在 jsobj.cpp 中的 EvalKernel 里:

bool
EvalKernel(JSContext *cx, uintN argc, Value *vp, EvalType evalType, JSStackFrame *caller,
           JSObject *scopeobj)
{
    ...

    /*
     * Per ES5, indirect eval runs in the global scope. (eval is specified this
     * way so that the compiler can make assumptions about what bindings may or
     * may not exist in the current frame if it doesn't see 'eval'.)
     */
    uintN staticLevel;
    if (evalType == DIRECT_EVAL) {
        staticLevel = caller->script()->staticLevel + 1;

    } else {
        /* Pretend that we're top level. */
        staticLevel = 0;

        JS_ASSERT(scopeobj == scopeobj->getGlobal());
        JS_ASSERT(scopeobj->isGlobal());
    }

    ...
}

  显然,你发现这个函数里有个叫 evalType 的参数,正是这个参数,决定了eval的是否在全局运行。如果是 DIRECT_EVAL,staticLevel 就是当前的上下文;否则 staticLevel=0,就是全局。

  顺藤摸瓜,我们又在jsinterp.cpp里发现以 DIRECT_EVAL 模式调用 EvalKernel:

bool
DirectEval(JSContext *cx, JSFunction *evalfun, uint32 argc, Value *vp)
{
    ...

    JSObject *scopeChain =
        GetScopeChainFast(cx, caller, JSOP_EVAL, JSOP_EVAL_LENGTH + JSOP_LINENO_LENGTH);

    if (!scopeChain || !EvalKernel(cx, argc, vp, DIRECT_EVAL, caller, scopeChain))
        return false;

    cx->regs->sp = vp + 1;
    return true;
}

  而调用DirectEval的地方,正是定义关键字的区域:

}
END_CASE(JSOP_NEW)

BEGIN_CASE(JSOP_EVAL)
{
    argc = GET_ARGC(regs.pc);
    vp = regs.sp - (argc + 2);

    if (!IsFunctionObject(*vp, &callee))
        goto call_using_invoke;

    newfun = callee->getFunctionPrivate();
    if (!IsBuiltinEvalFunction(newfun))
        goto call_using_invoke;

    if (!DirectEval(cx, newfun, argc, vp))
        goto error;
}
END_CASE(JSOP_EVAL)

BEGIN_CASE(JSOP_CALL)
BEGIN_CASE(JSOP_FUNAPPLY)
BEGIN_CASE(JSOP_FUNCALL)
{

 

  很明显,eval 和 new 他们一样,都当关键字处理了。也就是说,只有在代码里出现“eval(...)”这几个字的时候,才会传入DIRECT_EVAL,即在当前上下文内执行。

  可见,ECMA-262的eval有着关键字的特性!但并非是真正的关键字,因为关键字是不可以作为对象属性名出现的,例如window.this,window.var是错误的语法。但eval可以。

   

  那假如在上下文里声明了一个叫eval的变量,并且指向window.eval。那么又会如何呢?

<script>
function A()
{
    var eval = window.eval;

    eval("var a=1");
    eval("var b=1");
}

A();

alert(typeof a);    //undefined
alert(typeof b);    //undefined
</script>

 

  为什么是undefined呢?因为此 eval 就是原 eval 嘛~ 都是在内部执行了。(Chrome例外,当作eval的引用对待了,结果是number)

  但也不代表eval不能被覆盖:

<script>
eval = alert;

function A()
{
    eval("var a=1");        //var a=1
    window.eval("var b=1");    //var b=1
}

A();
</script>


  正常弹出两个对话框。
  可见,在上下文环境中,eval只要保持指向原始的那个函数,没被覆盖,就有着关键字的特征;否则就是一个叫eval的变量了。jsinterp.cpp中BEGIN_CASE(JSOP_EVAL){}里的代码也说明了这点。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消