码迷,mamicode.com
首页 > 其他好文 > 详细

词法作用域

时间:2016-08-07 10:56:47      阅读:168      评论:0      收藏:0      [点我收藏+]

标签:

  几天没有更新,这两天使周末,给大家整理了一几篇东西,有关于作用域的,闭包的,还有递归的,闭包和递归,对于大部分初次接触编程的人来说还是有些难度的,昨天,花了一点时间给大家整理了一下,今天,给大家上传上来,让大家看看,部分属于个人观点,如有错误,欢迎指出

  这一篇先给大家讲一讲词法作用域吧,但是讲之前我们需要先讲一讲变量名提升。

1. 变量名提升

    var num = 123;
    function foo1 () {
        console.log( num ); //undefined
        var num = 456;
        console.log( num ); //456
    }
    foo1();
  1. 预解析的过程
  2. 代码的执行过程

程序在执行过程,会先将代码读取到内存中检查,会将所有的声明在此时进行标记。所谓的标记就是 让 JS 解析器中有这个名字,后面在使用名字的时候,不会出现未定义的错误,这个标记过程就是提升。

声明:

  1. 名字的声明,标识符的声明(变量名声明)
    • 名字的声明就是让我们的解析器知道有这个名字
    • 名字没有任何数据与之对应
  2. 函数的声明
    • 函数声明包含两部分
    • 函数声明与函数表达式有区别,函数声明是单独写在一个结构中,不存在任何语句,逻辑判断等结构中
      • 什么是表达式:
    • 首先函数声明告诉解析器有这个名字存在,该阶段与名字声明一样
    • 告诉解析器,这个名字对应的函数体是什么
    function f() {
        function func() {
        } // 声明

        if ( true ) {
            function func2() {} // 函数表达式
        }
        var f = function func3 () {}; // 函数表达式

        this.sayHello = function () {}; // 函数表达式


        var i = 1;
        function func4 () {}  // 函数声明
        var j = 2;
    }

例:

    var num = 1;
    function num () {
        alert( num );
    }
    num();

分析:

  1. 预解析分析,提升名字
    • 首先提升名字,num
    • 再提升函数名,但是名字已经存在,因此只做了第二步,让名字与函数体对应和是哪个
    • 结论就是 代码中已经存在一个函数 num 了
  2. 开始执行代码,第一步从赋值语句开始执行
    • 给 num 赋值为 1
    • 覆盖函数
  3. 调用 num ,由于 num 中存储的是数字1,因此报错

例1:

    var num = 123;
    function foo1 () {
        console.log( num );
        var num = 456;
        console.log( num );
    }
    foo1();
  1. 预解析,提升 num 名字和foo1函数
  2. 执行第一句话: num = 123;
  3. 执行函数调用
    • 函数调用进入函数的一瞬间也需要预解析,解析的是变量名 num
    • 在函数内部是一个独立空间,允许使用外部数据,但是现在num 声明同名,即覆盖外面
    • 执行第一句,打印 num ,没有数据,undefined
    • 执行第二句 赋值: num = 456
    • 执行第三局 打印num,结果456

例2:

    if ( ! ‘a‘ in window ) {
        var a = 123;
    }
    console.log( a );
  1. 预解析, 读取提升 a, 有一个名字 a 存在了
  2. in 运算符: 判断某一个字符串描述的属性名是否在 对象中
    • var o = { name: ‘jim‘ }; ‘name‘ in o , ‘age‘ in o
    • 执行第一个判断: ! ‘a‘ in window
      • ‘a‘ in window 真
      • ! 得到 假
    • if 内部的赋值不进行
  3. 打印结果 a 的值为 undefined

例3:

    if ( true ) {
        function f1 () {
            console.log( ‘true‘ );
        }
    } else {
        function f1 () {
            console.log( ‘false‘ );
        }
    }
    f1();

老版本浏览器解析

  1. 预解析: 提升 f1 函数, 只保留最后提升的内容, 所以打印是 false
  2. 执行代码, 第一句话就是有一个 空的 if 结构

     if ( true ) {
    
     } else {
    
     }
    
  3. 执行函数调用, 得到 false

新版本浏览器解析

  1. 预解析,f1是函数表达式,直接执行if里面的,得到true

问题: function foo() {} var foo = function () {}; 之间的不同之处???

  1. 上面的语法是声明,可以提升,因此在函数定义的上方也是可以调用的
  2. 下面的语法是函数表达式,函数名是foo,它会提升,但提升的不是函数体
  3. 函数表达式也是支持名字语法的
    • 函数有一个属性 nume ,表示的是函数名,只有带有名字的函数定义,才会有
    • 但是,函数表达式的名字,只允许在函数内部使用,但IE在外部可以访问
    • () 可以将数据转换成表达式
    var foo = function func () {
    };

    func();

新的浏览器中, 写在 if, while, do-while 结构中的函数, 都会将函数的声明转换成 特殊的函数表达式 将代码

    if (...) {
        function foo () { ... }
    }

转换成

    if (...) {
        var foo = function foo () { ... }
    }

2. 词法作用域

2.1. 作用域

域表示的就是 范围, 即 作用范围. 就是一个名字在什么地方可以被使用, 什么时候不能使用.

2.1.1. 块级作用域

即块级的作用范围

    // 在 C , Java 等编程语言中, 下面的语法报错
    {
        var num = 123;  // int
        {
            console.log( num ); // => 123
        }
    }
    console.log( num ); // 报错

2.1.2. 在JS中采用词法作用域

所谓的词法(代码)作用域,就是代码在编写过程中体现出来的作用范围,代码一旦写好,不用执行 作用的范围就已经确定好了,这个就是所谓的词法作用域

在JS中才发作用域规则

  1. 函数允许访问函数外的数据
  2. 整个代码结构中只有函数可以限定作用域
  3. 作用规则首先使用提升规则分析
  4. 如果当前作用规则中有名字了,就不考虑外面的名字了

例子1:

    var num = 123;
    function foo() {
        console.log( num );
    }
    foo();

例子2:

    if ( false ) {
        var num = 123;
    }
    console.log( num ); // undefiend

例子3:

    var num = 123;
    function foo() {
        var num = 456;
        function func() {
            console.log( num );
        }
        func();
    }
    foo();   //456

例子4:

    var num1 = 123;
    function foo1() {
        var num1 = 456;
        function foo2() {
            num1 = 789;
            function foo3 () {
                console.log( num1 );
            }
            foo3();
        }
        foo2();
    }
    foo1();   // 789
    console.log( num1 );  // 123 //789会覆盖foo1中的num1,但是全局中的num1没有变化,所以还是123

2.2. 作用域链

可以发现只有函数可以制造作用域结构. 那么只要是代码, 至少有一个作用域, 即全局作用域. 凡是代码中有函数, 那么这个函数就构成另一个作用域. 如果函数中还有函数, 那么再这个作用域中就 又可以诞生一个作用域. 那么将这样的所有的作用域列出来, 可以有一个结构: 函数内指向函数外的链式结构.

例如:

    function f1() {

        function f2() {

        }
    }
    var num = 456;
    function f3() {
        function f4() {

        }
    }

变量的访问规则

  1. 首先看变量在第几条链上,在该链上看是否有变量的定义与赋值,如果有,直接使用
  2. 如果没有到上一级链上找(n-1级链),如果有直接用,停止查找
  3. 如果还么有再次往上找。。。直到找到全局链(0级),还没有就是is not defined
  4. 注意,切记 ,同级的链不可混合查找

例子:

    var num = 123;
    function f1() {
        console.log( num );
    }
    function f2() {
        var num = 456;
        f1();
    }
    f2();  //123

2.3. 补充

  1. 声明变量使用 var, 如果不使用 var 声明的变量就是全局变量( 禁用 )
  2. 因为在任何代码结构中都可以使用该语法. 那么再代码维护的时候会有问题. 所以除非特殊原因不要这么用.
  3. 下面的代码的错误
    function foo () {
        var i1 = 1  // 局部
            i2 = 2, // 全局
            i3 = 3; // 全局

    }
  1. 此时注意
    var arr = [];
    for ( var i = 0; i < 10; i++ ) {
        arr.push( i );
    }
    for ( var i = 0; i < 10; i++ ) {
        console.log( arr[ i ] );
    }
    // 一般都是将变量的声明全部放到开始的位置, 避免出现因为提升而造成的错误
    var arr = [],
        i = 0;
    for ( ; i < 10; i++ ) {
        arr.push( i );
    }
    for ( i = 0; i < 10; i++ ) {
        console.log( arr[ i ] );
    }

词法作用域

标签:

原文地址:http://www.cnblogs.com/brightking/p/5745657.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!