• 词法作用域


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

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

    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 ] );
        }
  • 相关阅读:
    【剑指offer】判断二叉树是否为平衡二叉树
    【剑指offer】数字在排序数组中出现的次数
    八大排序方法汇总(选择排序,插入排序-简单插入排序、shell排序,交换排序-冒泡排序、快速排序、堆排序,归并排序,计数排序)
    约瑟夫环问题-循环链表VS数组
    告别2014,你是否感谢这一年的自己?
    浅谈WEB页面提速(前端向)
    HTML5- Canvas入门(七)
    浅谈WEB安全性(前端向)
    是时候搁置Grunt,耍一耍gulp了
    前端神器avalonJS入门(二)
  • 原文地址:https://www.cnblogs.com/brightking/p/5745657.html
Copyright © 2020-2023  润新知