javascript之做用域三(理解做用域链)

3、做用域链

js访问一个变量时会优先在该做用域内(访问时的那个做用域)寻找是否声明过这个变量,若是该变量已经存在,则直接使用它的值,不然会寻找该做用域的‘父做用域/上级做用域',依次类推,直到找到全局做用域为止。通俗地讲,当声明一个函数时,局部做用域一级一级向上包起来,就是做用域链。bash

首先看一个多级做用域的栗子:闭包

//多级做用域
//======>此处是1级做用域
var gender = "男";
function fn(){ // ======>从这里开始是2级做用域
    //gender 能够访问  gender是全局做用域的变量,任何地方均可以访问
    //age    能够访问
    //height 不能访问

    return function(){ // ======>从这里开始是3级做用域
        //gender  能够访问
        //age     能够访问
        //height  能够访问
        var height = 180;
    }
    var age = 5;
}
复制代码

因为做用域是相对于变量而言的,而若是存在多级做用域,这个变量又来自于哪里?这个问题就须要好好地探究一下了,咱们把这个变量的查找过程称之为变量的做用域链函数

做用域链的意义:查找变量(肯定变量来自于哪里,变量是否能够访问)post

简单来讲,查找一个变量来自哪里,可否被访问,须要如下四步:ui

  1. 查看当前做用域,若是当前做用域声明了这个变量,能够直接访问
  2. 查找当前做用域的上级做用域,也就是当前函数的上级函数,看看上级函数中有没有声明,有就返回变量,没有继续下一步
  3. 再查找上级函数的上级函数,直到全局做用域为止,有则返回,无则继续
  4. 若是全局做用域中也没有,咱们就认为这个变量未声明(xxx is not defined)

这四步操做就描述了整个做用域链及做用域链如何查找变量的过程。spa

  1. 当执行函数时,老是先从函数内部找寻局部变量
  2. 若是内部找不到(函数的局部做用域没有),则会向建立函数的做用域(声明函数的做用域)寻找,依次向上

代码分析:

  1. 当执行fn1时,建立函数fn1的执行环境,并将该对象置于链表开头,
  2. 而后将函数fn的调用对象放在第二位,最后是全局对象,
  3. 做用域链的链表的结构是fn1->fn->window。
  4. 从链表的开头寻找变量a,即fn1函数内部找变量a,找到了,结果是20。
  5. 一样,执行fn2时,做用域链的链表的结构是fn2->fn->window。
  6. 从链表的开头寻找变量a,即fn2函数内部找变量a,找不到,
  7. 因而从fn内部找变量a,找到了,结果是10。
  8. 最后在最外层打印出变量a,直接从变量a的做用域即全局做用域内寻找,结果为1。

下面咱们来举几个特殊的栗子: 例子1:code

function fn(callback){
    var age = 20;
    callback();
}
fn(function(){
    console.log(age);//报错
    //1.在当前做用域没有查找到age
    //2.查找上一级做用域:全局做用域
    //为什么是全局做用域?
    //由于看上一级做用域,不是看函数在哪调用,而是看函数在哪编写的。
    //这种特别的做用域,叫作“词法做用域”
})
复制代码

这个栗子比较特殊,可能不少人会认为输出20,由于函数调用的地方是fn函数内部,恰巧age又是声明在这个函数内部的,理所应当输出20。这是错误的! 如今分析下做用域链如何查找变量的:cdn

  1. console.log(age)的时候,在当前做用域并无查询到age变量。因此查找上一级做用域。
  2. 上级做用域是谁?这里须要引出一个概念,查找函数上级做用域,不是看函数在哪调用,而是看函数在哪编写。因此这样来看,上级做用域就是全局做用域。
  3. 在全局做用域中并无声明age变量,因此console.log(age);就会报错。

例子2:对象

var name="张三";
    function f1(){
        var name = "abc";
        console.log(name);
    }
    f1(); // abc
复制代码

若是查找一个变量时,在当前做用域找到变量,无论上级、上上级有没有同名变量都不会再去寻找。blog

例子3:

var name="张三";
    function f1(){
        console.log(name);
        var name = "abc";
    }
    f1(); // undefined
复制代码

若是这个栗子能看懂说明已经了解变量提高,若是不懂,参考后面的文章。

例子4:

var name = "张三";
    function f1(){
        var name = "abc";
        return function(){
            console.log(name);
            console.log(age);
        }
        var age = 18;
    }
    var fn = f1();
    fn();
    //abc 
    //undefined
复制代码

这个栗子咋一看可能有点懵,可是记住以前一句很重要的话,查找函数上级做用域,不是看函数在哪调用,而是看函数在哪编写。

例子5:

var name="张三";
    function f1(){
        return {
            say:function(){
                console.log(name);
                var name="abc";
            }
        }
    }
    var fn=f1();
    fn.say();//undefined
复制代码

当前做用域查到了变量,则不会再继续寻找,直接返回该变量的值,这里打印的时候变量声明可是未赋值,因此输出undefined。

若是以上几个栗子都能看懂,说明你已经掌握了做用域&做用域链,学好做用域和做用域链能够为理解闭包打下很好的基础。

6 闭包

提到做用域就不得不提到闭包,简单来说,闭包外部函数可以读取内部函数的变量。

优势:闭包能够造成独立的空间,永久的保存局部变量。

缺点:保存中间值的状态缺点是容易形成内存泄漏,由于闭包中的局部变量永远不会被回收

产生闭包的根本缘由是做用域链

7 总结

  1. 产生闭包的缘由是由做用域链引发的
  2. 函数嵌套函数,被嵌套的函数就能够称为闭包
  3. 子函数可使用父函数的变量(访问其余函数内部的局部变量)
  4. 让变量始终保存在内存中,避免自动垃圾回收(其实上面的例子中就已经用到了的)
  5. 对外提供公有属性和方法

详细讲解能够参考我写的闭包系列:

闭包 juejin.im/post/5d4e40…

相关文章
相关标签/搜索