设为首页 加入收藏

TOP

函数作用域链
2019-05-13 13:52:49 】 浏览:123
Tags:函数 作用

函数作用域:

函数作用域可以分为两个内容来理解:

1.函数作用域的基础概念:函数内定义的变量,不能在函数之外的任何地方访问,而这个函数可以访问其范围内的任意变量和函数,以及其父函数有权访问的任何其他变量。

2.特性:函数作用域内,依旧遵从变量提升(包括函数提升)和顺序执行。

PS:变量提升是块级作用域和函数作用域的主要区别。

第一点,很容易理解,就是函数不仅可以访问自己内部定义的变量,还可以访问外部函数中的变量,以及外部函数的外部函数的变量....比如这样

    //如果你不喜欢用控制台,可以删了下面这行代码
    // 并把后面的log改为alert
    var log = console.log.bind(this);
    var x = 4;
    function add() {
        var i = 1;
        log(i+x);
    }
    add();

第二点,就是顺序执行和变量提升(了解的可以直接跳过)

顺序执行:从上到下,一条一条的执行

变量提升:函数体内变量声明和函数声明,会被提升到函数体的顶部,所以你可以在变量声明之前访问数据,函数声明之前调用函数。这样,表面上看起来违背了顺序执行,其实并不是的

    var log = console.log.bind(this);
    //函数声明提升
    add();          //add
    function add() {
        log("add");
    }
    //函数声明提升的效果是这样
    function add(){
        log("add")
    }
    add();

如果只是简单的顺序执行,应该会抛出异常(add is not defined),其实,Js是在函数提升之后,才顺序执行的,一切正常,变量提升,又有些不同

    var log = console.log.bind(this);
    //变量提升
    log(i);         //undefined
    var i = 3;
    log(i);         //3

    //变量提升的效果是这样
    var i;
    log(i);
    i = 3;
    log(i);

同样的,简单的顺序执行,在第一个alert(i)处,会抛出异常(i is not defined),不过,Js在提升变量后的,但是变量的提升并不会将它的赋值过程也提升,赋值是在顺序执行中完成的,所以你在赋值操作之前访问i,它只能拿出一个undefined来敷衍你了,所以一般建议将变量声明和赋值操作,全部写在函数体的顶部,防治出现undefined。

总的来说呢,就是先提升,后顺序执行。

那么,还有个问题就是

    var func = function(){
        alert("我是谁?")
    }
这个函数表达式是个什么提升?你可以自己试一下。

那么函数作用域是什么,就是把2的特性,用到1里面(函数体内),下面是一个简单的测试:

    var log = console.log.bind(this);
    var x = "global x";
    function add() {
        log(x);         //1
        var x = "local x";
        log(x);         //2
    }
    add();

那么,1会输出什么,2会输出什么?不要忘记特性中的提升!!!在全局作用域内,你会用,在函数体内,你也会用了吧。

1输出:undefined

2输出:local x

总结两句,刚开始我一直会理解错,就是因为,会自然而然的用到从上到下的顺序执行,上面1的结果就变成了global x,但JS拥有函数作用域,它有变量提升,那么,只要你在变量提升之后,再顺序执行,就不会错了。当然,eva l()是不会有变量提升,这里不做深入讨论。

还有一点,就是if语句和for循环语句,使用了C语言风格的语法把多条语句组合到一个代码块中,而Js没有块级作用域(在块级作用域中,变量在离开定义的块级代码后被立即回收),所以,zhe语句代码块中的数据,在整个函数内部可以被访问。

        function f() {
            alert("1. i:"+i+" a:"+a);
            for(var  i = 0; i<3; i++){
                var a = 2;
            }
            alert("2. i:"+i+" a:"+a);
        }
        f();
两次输出结果就是:

1. i:undefined a:undefined

2. i:3 a:2

好像没什么多余的要说的,let语句以后再说吧。

函数作用域链:

执行环境:一个函数执行时的环境。定义了变量或者函数有权访问的其他数据

变量对象:环境中定义的所有变量和函数都保存在这个对象中(我们无法访问变量对象,但后台处理数据时会用到它)

活动对象[[scope]]:如果环境是一个函数,那就将活动对象作为变量对象,活动对象中还包含arguments对象

作用域链:当代码在一个环境中执行时,会创建变量对象的一个作用域链

三者的的关系就是,函数执行时,就产生执行环境,执行环境中的所有函数、变量成为变量对象中的属性,而这个变量对象+外部执行环境中的变量对象+外外部的...+全局对象,就成了这个函数的作用域链(就是一个变量对象的有序集合)。

尽力去理解上面几个概念,不理解也没关系,毕竟概念和例子一起食用,见效最快。

        var x = 1;
        function func(i){
            var y = 2;
            alert(x);           //undefined
            var x = i;
            function add() {
                alert(x+y);     //6
            }
            add();
        }
        func(4);

如果你不是很清楚作用域链的话,可以分三步来分析

第一步,它是如何“链”的

先说func函数,我们可以轻易地找到它的所有外部函数,那就是func()→全局,而作用域链也是如此,当func函数被调用时,作用域链就是func(4)活动对象→全局变量对象。

第二步,将什么东西“链”起来了

上面也说了,其实链起来的是变量对象(活动对象和变量对象的区别,见上面的概念),那活动对象中,有什么内容呢?内容如下:函数内部定义的局部变量以及传入的参数和arguments对象都会作为活动对象的属性。活动对象本身并不是一个对象,更像一个对象列表。声明一点,活动对象,是存在有JS引擎中的,我们并不能够查看它,所以你才会头疼。对于上面这个例子,func(4)的作用域链:


看吧,就是简单的把变量对象“链”了起来。前面的第一步,是为了在你不熟悉的时候,快速找到作用域链的一个野路子,正确的路子,应该是用执行环境来描述的,像这样:对于func(),我们可以轻易的找到它自己的执行环境以及包含它的父执行环境,那就是func()执行环境→全局环境。基本上,两者是一一对应的,只是概念上的差距比较大。

第三步,作用域链的用途

作用域链保证了执行环境有权访问的所有变量和函数的有序访问。这句话,关键词就是“有权”和“有序”,有权就是子执行环境可以访问父执行环境(访问活动对象中的属性),但父环境不能访问子环境中的任何变量和函数。有序,向上搜索,是为了处理同名变量的问题。比如func(4)执行到alert(x)时,两个活动对象中都有x,向上搜索,就是从自己的活动对象开始搜索,搜索到了直接弹出,搜索不到,就搜索父执行环境,一次次向上。

大概就是这么个步骤,例子是比较简单的,现在可以看一下add()的作用域链:


因为add函数内部,没有局部变量,也没有传入的参数,更没有局部函数,所以变量对象只有一个系统指定的对象arguments。在alert(x+y)是,要引用变量x和y,然后就顺着作用域链往上走搜索,在func(4)的活动对象中找到了变量x=4和y=2,不再继续往上搜索,最后弹出6。

还有一点要注意的是,在函数内部直接为变量赋值,不进行声明,将作为全局变量对象中的属性,如下:

       function f() {
           x = 4;
       }
       alert(x);        //error
       f();
       alert(x);        //4
就是这个结果了,alert(x)是在全局环境中的,无权访问f函数内部的数据,第二次弹出了4,所以说,x在f函数执行之后,作为全局变量对象的属性。

总结下,刚开始,我不懂作用域链的时候也觉得挺难的,后来查了一些书和博客,以为差不多懂了,然后就写了这篇博客,前后删改了3、4次了,拖了一周,发现自己还是有不太懂的地方,又是漫漫搜索路,最后终于写完了,删改的过程也是蛮痛苦的,好几次不想写了,都想直接把博客全删了。关于为什么写博客:为什么呢应该写博客|刘未鹏


】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇基于服务器XMLCRUD操作工具类 下一篇线程和进程

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目