惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
B
Blog RSS Feed
宝玉的分享
宝玉的分享
腾讯CDC
博客园_首页
T
Tailwind CSS Blog
月光博客
月光博客
博客园 - 司徒正美
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
M
MIT News - Artificial intelligence
A
About on SuperTechFans
云风的 BLOG
云风的 BLOG
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
有赞技术团队
有赞技术团队
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
大猫的无限游戏
大猫的无限游戏
MongoDB | Blog
MongoDB | Blog
博客园 - 聂微东
V
Visual Studio Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
SecWiki News
SecWiki News
美团技术团队
P
Privacy International News Feed
H
Help Net Security
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Microsoft Security Blog
Microsoft Security Blog
Know Your Adversary
Know Your Adversary
Y
Y Combinator Blog
D
DataBreaches.Net
Project Zero
Project Zero
T
The Blog of Author Tim Ferriss
Cyberwarzone
Cyberwarzone
C
Cybersecurity and Infrastructure Security Agency CISA
C
Cisco Blogs
S
Schneier on Security
G
GRAHAM CLULEY
博客园 - 三生石上(FineUI控件)
Cisco Talos Blog
Cisco Talos Blog
小众软件
小众软件
Forbes - Security
Forbes - Security
D
Docker
T
Tenable Blog
S
Secure Thoughts
雷峰网
雷峰网
S
Security @ Cisco Blogs
T
The Exploit Database - CXSecurity.com
The Cloudflare Blog
博客园 - 【当耐特】
Spread Privacy
Spread Privacy
阮一峰的网络日志
阮一峰的网络日志

博客园 - Franky

manifest 和 application cache 一套有趣的 javascript题. URL 中,查询字符串与HTML实体冲突,可能带来的问题. 从 null >= 0 说起 深入剖析,什么是eval的直接调用. 凑凑热闹,给eval做个科普. 浏览器静态资源的版本控制新思路.强制更新指定资源缓存.的探讨 IE6 与 GZIP, BUG汇总 ES3和ES5中函数调用(function call) 的浏览器实现差异. 你应该了解的 一些web缓存相关的概念. html5 performance 的应用尝试. HTML5 performance API 草案. Object.defineProperty 接口浏览器实现的bug.和疑惑 window.open 浏览器差异. Chrome v8引擎 对多行注释,做词法分析时的bug. IE9的特性变化,(收集贴) 关于p3p 简洁策略,以及浏览器的支持情况. 心神不宁啊... UI线程的阻塞.
从ES5 的 函数声明与函数表达式说起.
Franky · 2012-12-13 · via 博客园 - Franky

我们先从阿灰 的蛋疼的例子开始.

 demo1:

  function test() {

        var x = 1;

        with ({x: 2}) {

            eval('function foo() { console.log(x); }');

            eval('var bar = function() { console.log(x); }');

        }

        foo();

        bar();

    }

    test();

大多数按照ES5实现的引擎(除了Opera12-)的结果. 打印的都是 两个2 .我必须先强调下.这个结果,按照标准是错的. 按照标准都应该是 1 , 2 . Opera12- 会打印两个1 .这个我们就不讨论了.因为错的更离谱了.

开始正题:

为什么说两个2不对呢. 这就要从ES5的函数声明,以及函数表达式说起了.

坦白说.写这篇东西的时候我很抗拒.因为要说清楚这里边的细节.实在涉及到太多的东西了.如果每个涉及到的概念都要说到位.有些浪费对ES5很熟悉的朋友的时间.而我并不认为不熟悉ES5的朋友通过一篇详细的介绍就能建立起正确的认识.所以还是一切从简吧.

我在这里假设您知道什么是 函数声明的概念,以及函数表达式的概念,以及二者在语法层面的区别.以及 Lexical Environments , LexicalEnvironment,  VariableEnvironmet 以及之间的关系 以及 Environment Records,  Declarative Environment Records, Object Environment Records 这些东西的相关概念.

     ES5 中函数定义章节描述的两种定义方式 :

          1. 函数声明

          2. 函数表达式

     这两种方式定义时不仅仅是语法上的差异.还有创建函数对象时的一些细节差异. 我们要讨论的就是 函数声明使用其所在执行环境的当前的VariableEnvironmet  作为创建函数对象时提供的scope参数.而函数表达式则使用 LexicalEnvironment 作为scope参数.

这就导致我们上面例子中的最直接的问题了.

     我们知道,在非严格模式下,ES5是允许使用with(expressionStatement 语法的. 但是我们要先明确几件事.

          1. Statement  语法产生式中.是不能出现 FunctionDeclaration 的. 相对于我们类似的 catch从句的 block 语法产生式也是如此.(显然所有浏览器都没有按标准实现这部分规范,也就是说我所见过的所有javascript引擎的都允许在with,catch里 甚至if statement中等等位置使用函数声明)

          2. 我们的例子和第一条并不冲突. 因为我们前面例子中的函数声明是放在eval中. 而eval code是Program .是允许进行函数声明的.

          3. with会在其作用范围内 创建一个新的LexicalEnvironment .并把其所在执行环境相关联的LexicalEnvironment 设置为这个新创建的LexicalEnvironment .(也就是说with改变了当前执行环境的LexicalEnvironment)

          4. with并没有改变VariableEnvironmet.在改变LexicalEnvironment 前, LexicalEnvironment 和 VariableEnvironmet指向相同的引用.(with强迫他俩各玩各的了)

          5. with 还会为这个LexicalEnvironment 创建一个object environment record .目的自然是 with(expression) 中的ToObject(expression)的结果作为标识符搜索的整个责任链的顶端.

          6. 非严格模式下的eval内部的代码被执行时.会使用eval的调用环境LexicalEnvironment,以及VariableEnvironmet  作为其内部代码的执行环境的对应的 Lexical Environments(注意我这里是复数.且单词间有空格,希望你理解这里的意思.不详细解释了)

     现在问题来了.你应该注意到按照我们的已知条件. foo是函数声明.那么它的标识符查找的下一个 Lexical Environment 显然是原来的 VariableEnvironmet. 而 bar 的则是with创建的新的 LexicalEnvironment . 那么显然整个例子的结果就应该是1,2 . 但遗憾的是,我们还没有找到一款已知的javascript引擎,在此处的实现是符合标准的....

     但是知道这些,并不是我们今天讨论的重点.我们的重点是.为啥标准会这样定义.

     以下部分为我个人猜测.  

demo2:

function test(){
    var x = 1;
   

    with({x:2}){

        function foo(){
            console.log(x);
        }

        var bar = function(){
                console.log(x);
        };
    }
    
    foo();

    bar();

}

test();

     如果我的对标准的解读没有错误的话。 我就可以断定此处规范的制定者,自己陷入混乱. 他似乎就是在考虑类似demo2情形是.思路是这样的:

     foo的函数声明,所定义的函数对象,不应该受到with的影响. 但是他似乎忘记了两件事. 

          1. 函数声明是在 控制器一进入执行环境,进行变量初始化阶段进行的. 而with 语句的执行,却是在解释执行期进行的. 显然foo的函数定义是早于 with执行的.当with执行时篡改当前执行环境相关联的  LexicalEnvironment 时. 所有函数声明早就处理结束了.foo.[[Scope]]早就引用了最初的LexicalEnvironment 了.根本就不应该受到with的影响. 所以这个设计完全是多余的.. 

          2. 设计者忘了Statement 以及 blok 两个语法产生式中根本就不存在FunctionDeclaration  .那还折腾什么呢? 所以从标准角度.也许根本就不应该生生的把Lexical Environments 拆分成 LexicalEnvironment 和 VariableEnvironmet  . 要对付with和catch前插的责任链节点. 只需要概念描述下即可.但可能是为了描述精准吧?毕竟他要描述with本身特有的那种特性.总之这个屁股并没有在ES5里被擦干净.

          

ps: 虽然所有已知引擎都没有遵守第二条. 但我们讨论的毕竟是标准.而不是实现的差异.

    ps: catch从句有类似的创建并篡改当前执行环境LexicalEnvironment 的能力.不过它对应的产生式是 block.且catch并不会使用object environment record

          ps: SpiderMoneky的早期版本 包括中间的各种跟猴子的分支.都支持 BlockFunctionDeclaration  .这种被称为语句块函数声明的特殊的,非标准语法.它会导致上面的demo2的结果有差异.不属于本文讨论范围

  简单来说就是:

     typeof fn;// undefined

     {

         function fn(){}

     }

     最后. 似乎ES6草案中 函数声明也不再使用VariableEnvironmet  作为其[[Scope]]了. 不知道标准委员会中,是否有人意识到了这个问题.而做出了修正. 暂时没精力继续去问了. 我对ES6的态度还处于观望状态.什么时候release状态了.再去跟进好了..

     好吧.就这样吧. 感谢阿灰写出了这个蛋疼的例子. 感谢 Bosn 和 kenny 还有abcd同学花时间大家一起讨论它. 最后.真心话.相比ES5 取消了 scope chain 的概念而用 内外层词法环境来代替. 我更喜欢ES3的描述.因为那种描述直接是数据结构上的描述.更适合码农啊显然.  


 最后,附加一点东西, 因为前阵子有看到,有朋友认为 function declaration 不能被当做statment那样出现在 block中,是ES5严格模式的一个限制. 下面来说说这个问题 .

关于 函数声明出现在block 或 其他statement应该出现的地方的问题, ES5 12.0 章节末尾有这样一段描述:

Several widely used implementations of ECMAScript are known to support the use of FunctionDeclaration as a Statement. However there are significant and irreconcilable variations among the implementations in the semantics applied to such FunctionDeclarations. Because of these irreconcilable differences, the use of a FunctionDeclaration as a Statement results in code that is not reliably portable among implementations. It is recommended that ECMAScript implementations either disallow this usage of FunctionDeclaration or issue a warning when such a usage is encountered. Future editions of ECMAScript may define alternative portable means for declaring functions in a Statement context.

基本上就是说,已知的绝大多数ES引擎,都支持把函数声明,作为一个语句来处理的这种实现.   而实际上这带来了语意冲突.  ES5的建议是禁止这种行为, 或者发出一个警告..  并且在将来的ES版本中 基于这两种建议中的一个.来作为标准的行为… 

也就是说,更加强调ES引擎,不应该让函数声明 出现在block 等地方…   所以部分引擎(比如v8和猴子)在实现ES5 严格模式时, 顺便也明确禁止了这种行为(但应该明白, 这个严格模式限制,并不来自ES5 严格模式的官方限制.而是引擎实现者自己出于某种考虑而实现的)… 但我个人认为. 这个约束是多余的. 因为本身我们观察产生式就会发现.  本来就不应该出现这种支持的. 也就是说, 非严格模式变体 , 也不应该出现这种使用函数声明的方式… 何必多此一举的在样模式中强调呢? .. 哦 当然, 出于对事实现状的尊重,如果粗暴进制非严格模式变体代码也抛异常的话, 老代码可能就会出问题….所以 这也许是 部分引擎实现者的弥补措施吧.

至于ES6如何规定这里, 我们拭目以待吧. 坐等ES6 release .

另附ES5严格模式 一篇链接 : http://www.cnblogs.com/_franky/articles/2184461.html