About

<#TEMPLATE_INCLUDE_NINEPAGE_ABOUTME#>
  • May

    27

    词法作用域就是指定义代码的时就决定了作用域范围,解释一个术语时最好的方法就是举一个简单的例子,下面这段虽然的  JS 代码,但它适用于所有包含词法作用域语言的概念:

    <script>
    var str = "global";
    function test(){
        alert(str);
        var str = "local";
        alert(str);
    }
    test();
    </script>

    如果不了解词法作用域,可能会认为上面的代码会先输出 global,然后再输出 local;事实际上它是先输出 undefined,然后再输出  local。

    JS 通常情况下定义一个函数后,它就是词法作用域,这意味着它运行在自己定义的作用域中,而不是运行在执行代码的作用域中。JS 解析器在加载 JS代码后并不会立刻执行,而是先解析 JS 代码,上面的函数实际上会被编译成如下类似的代码,然后再运行编译后的代码:

    <script>
    var str = "global";
    function test(){
        var str;
        alert(str);
        str  = "local";
        alert(str);
    }
    test();
    </script>

    但类似 JS 这样的语言本身也有例外情况,比如函数本身可以通过构造函数来动态的创建,并且 JS 解析器可以读取动态创建的 JS代码,并编译它们然后运行,但此时动态创建的函数并不使用词法作用域,编译后的代码当作顶级函数来编译。

    <script>
    var str = "global";
    function test(){
        var str = "local";
        return new Function("return str")
    }
    alert(test()());
    </script>

    所以上面的代码运行结果输出 global。但这种情况并不适用于所有需要预编译或需要编译后执行的代码,比如类似 Flash AS 这样需要预编译的代码,因为所有 Adobe 官方的 Flash 运行时环境都只有加载 ABC 码和解码的功能,运行时环境本身没有编译 ABC 码的功能。

    May

    25

    <script>
    var f = function fact(x){
        if(<=1)
            return 1;
        else 
            return x * fact(x-1);    
    }
    alert(f(2));
    alert(fact(2));
    </script>

    这是一个看起来像是有命名的函数直接量。为什么说它看起来像是有命名呢?因为要分情况。

    在 IE 8 中测试结果发现它真的就像命名函数一样,甚至可以将 alert(fact(2)); 提前到函数直接量表达式的前面;

    但在 Google Chrome 35 中上面的代码最后一行会产生一个运行时错误(因为访问了一个不存在的定义)。如果将上面代码中的最后两种交换位置,在 Chrome 35 中不会输出任何内容,因为当 alert(fact(2)); 运行产生错误时会静默失败,然后后续代码会全部中止运行。

    而事实上这种看起来好像有命名的函数直接量只允许 JS 1.5 后的版本中使用,并且是只允许的在函数体内用这个名字来引用自身(比如类似上面的嵌套函数)。

    所以,如果不是想要展现自己在特定平台开发的编程技巧,就算有脚本标准,最好也不要使用奇葩的写法(因为不同浏览器厂商未必都遵循标准),除非那个脚本语言是被垄断性的只能运行在特定的平台,比如 ActionScript。

    May

    25

    使用 with 语句虽然能使用代码变简洁,减少代码的输入量,但它并不被开发者们推荐(甚至在 ECMAScript 5 的严格模式中遭到了禁用)。因为在使用 with 语句时,代码的运行速度会比不使用时更慢。并且在 with 语句内定义的变量作用域情况比较复杂,也会让初学者迷惑。看以下样例代码:

    <script>
    function fun(){
        var x = 9;
        var y = 10
        with(Math){
            var larger = max(x,y);//显示声明,这个变量是定义在函数内的,作用域是在函数层级,与x、y相同。
        }
        alert(larger);//10
        alert(Math.larger);//undefined;虽然在with内声明,但并不属于with的目标对象。
    }
    fun();
    alert(larger);//访问不存在的变量,这里就会产生一个异常,因为在函数外没有定义。
    </script>

    然后再试一下隐式声明:

    <script>
    function fun(){
        var x = 9;
        var y = 10
        with(Math){
            larger = max(x,y);//隐式声明一个变量时它的作用域是全局的
        }
        alert(larger);//10
        alert(Math.larger);//undefined;
    }
    fun();
    alert(larger);//10,访问全局的变量。
    </script>

    备注:由于 AS 与 JS 遵循相同的 ECMA-262 标准,尤其是熟悉和精通 AS1、AS1.1、AS2 的人可能会对这种 JS 的作用域更熟悉(AS1.1 与 JS 之间完全可以轻松的共享代码)。

    May

    25

    JS 运行时环境(JS 解析器)需要先加载 JS 代码时,然后解析(编译)JS 成为可执行代码,然后运行可执行语句。而函数定义实际上是在解析时发生(也就是在运行 JS 代码之前)。

    <script language="javascript"> 
    alert(f(4));
    var f = 0;
    function f(x){
     return x * x;
    }
    alert(f);
    </script>

    先输出 16,再输出 0。这种解析过程往往被称为“函数向前引用”,就好像函数定义被提前到了所有可执行语句之前。实际函数定义在加载 JS 并解析时就动态生成了一个与函数名相同的变量名,所以当后面相同的变量名再次出现时覆盖了前面变量的定义(函数定义和变量定义并不在同时发生)。

    May

    24

    JavaScript 本身并不令人讨厌,比如在一个独立的客户端程序中嵌入特定版本的 JS 解析器,就会非常方便。

    但它却被 Web 开发者们厌恶的最大理由就是需要到处调试,因为不同的浏览器解析出来的结果往往不同,下面用一行代码示例说明 JavaScript 是一种值得唾弃的语言。随意创建一个 *.html 文件,并输入如下一行代码。

    <a href="javascript:5%2">A</a>

    用 IE 8 打开后测试,返回一个字符串作为新文档显示结果 1,并且浏览器的返回按扭变成可用,基本上可以认为它的解析过程与我们相要的效果相当(以 《JavaScript 权威指南》一书中介绍的为标准);但当使用 Google Chrome 35 版本作为浏览器测试时,发现点击没有任何效果。

    对它稍作修改如下:

    <a href="javascript:(5%2)+''">A</a>

    IE 8 与 Chrome 35 都返回新页面显示结果 1,这说明 Chrome 对基本数据类型有自动转型的功能,但在第一次的代码中它并没有自动转型。其中与 IE 8 仍然有细节上的差异,IE 8 在显示结果 1 时,产生新的历史记录页面,浏览器的返回按扭变成可用,但 Chrome 返回按扭仍然不可用。

    这如何让前端开发者们不唾弃这种语言,尤其是 Web 前端开发者,每写一个功能甚至一行代码,就需要从一种浏览器调试到另一种浏览器,测试功能与效果是否能实现需求相同(这里例举的仅仅只是两款主流的浏览器其中的一个版本)。

    More...