JS进阶之---做用域,做用域链,闭包

1、做用域:前端

  在JavaScript中,咱们能够将做用域定义为一套规则,这套规则用来管理引擎如何在当前做用域以及嵌套的子做用域中根据标识符名称进行变量查找。这里的标识符,指的是变量名或者函数名。web

  JavaScript中只有全局做用域与函数做用域(由于eval咱们平时开发中几乎不会用到它,这里不讨论)。编程

  做用域与执行上下文是彻底不一样的两个概念。闭包

 

  JavaScript代码的整个执行过程,分为两个阶段,代码编译阶段与代码执行阶段。编译阶段由编译器完成,将代码翻译成可执行代码,这个阶段做用域规则会肯定。执行阶段由引擎完成,主要任务是执行可执行代码,执行上下文在这个阶段建立。函数式编程

      

  做用域在编译阶段肯定规则,但是为何做用域链却在执行阶段的建立过程肯定?之因此有这个疑问,是由于你们对做用域和做用域链有一个误解。咱们上面说了,做用域是一套规则,那么做用域链是什么呢?是这套规则的具体实现。函数

 

 

2、做用域链this

  做用域链,是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。spa

  简单说就是函数能够访问的一系列的做用域中的变量对象。翻译

var a = 20;

function test() {
    var b = a + 10;

    function innerTest() {
        var c = 10;
        return b + c;
    }

    return innerTest();
}

test();

  在上面的例子中,全局,函数test,函数innerTest的执行上下文前后建立。咱们设定他们的变量对象分别为VO(global),VO(test), VO(innerTest)。而innerTest的做用域链,则同时包含了这三个变量对象,因此innerTest的执行上下文可以下表示。 3d

innerTestEC = { VO: {...}, // 变量对象
    scopeChain: [VO(innerTest), VO(test), VO(global)], // 做用域链
    this: {} }

  不少人会误解为当前做用域与上层做用域为包含关系,但其实并非。以最前端为起点,最末端为终点的单方向通道我认为是更加贴切的形容。如图。

      

 

 

3、闭包

  当函数能够记住并访问所在的做用域(全局做用域除外)时,就产生了闭包,即便函数是在当前做用域以外执行。简单来讲,假设函数A在函数B的内部进行定义了,而且当函数A在执行时,访问了函数B内部的变量对象,那么B就是一个闭包。

  咱们知道,函数的执行上下文,在执行完毕以后,生命周期结束,那么该函数的执行上下文就会失去引用。其占用的内存空间很快就会被垃圾回收器释放。但是闭包的存在,会阻止这一过程。因此,经过闭包,咱们能够在其余的执行上下文中,访问到函数的内部变量。

var fn = null;

function foo() {
    var a = 2;
    function innnerFoo() { 
        console.log(a);
    }
    fn = innnerFoo; // 将 innnerFoo的引用,赋值给全局变量中的fn
}

function bar() {
    fn(); // 此处的保留的innerFoo的引用
}

foo();
bar(); // 2

  在上面的例子中,foo()执行完毕以后,按照常理,其执行环境生命周期会结束,所占内存被垃圾收集器释放。可是经过fn = innerFoo,函数innerFoo的引用被保留了下来,复制给了全局变量fn。这个行为,致使了foo的变量对象,也被保留了下来。因而,函数fn在函数bar内部执行时,依然能够访问这个被保留下来的变量对象。因此此刻仍然可以访问到变量a的值。咱们称foo为闭包。

       

 

 

4、 闭包的经常使用场景

  一、延迟函数setTimeout

function fn() {
    console.log('this is test.')
}
var timer = setTimeout(fn, 1000);
console.log(timer);

  执行上面的代码,变量timer的值,会当即输出出来,表示setTimeout这个函数自己已经执行完毕了。可是一秒钟以后,fn才会被执行。这是为何?

  按道理来讲,既然fn被做为参数传入了setTimeout中,那么fn将会被保存在setTimeout变量对象中,setTimeout执行完毕以后,它的变量对象也就不存在了。但是事实上并非这样。至少在这一秒钟的事件里,它仍然是存在的。这正是由于闭包。

  很显然,这是在函数的内部实现中,setTimeout经过特殊的方式,保留了fn的引用,让setTimeout的变量对象,并无在其执行完毕后被垃圾收集器回收。所以setTimeout执行结束后一秒,咱们任然可以执行fn函数。

 

  二、模块

(function () {
    var a = 10;
    var b = 20;

    function add(num1, num2) {
        var num1 = !!num1 ? num1 : a;
        var num2 = !!num2 ? num2 : b;

        return num1 + num2;
    }

    window.add = add;
})();

add(10, 20);

  上面的例子使用函数自执行的方式,建立了一个模块。add是模块对外暴露的一个公共方法。而变量a,b被做为私有变量。

  在面向对象的开发中,咱们经常须要考虑是将变量做为私有变量,仍是放在构造函数中的this中,所以理解闭包,以及原型链是一个很是重要的事情。

 

  三、柯里化
  在函数式编程中,利用闭包可以实现不少炫酷的功能,柯里化算是其中一种。

 


5、留个问题,验证本身有没有理解闭包:

  利用闭包,修改下面的代码,让循环输出的结果依次为1, 2, 3, 4, 5

for (var i=1; i<=5; i++) { 
    setTimeout( function timer() {
        console.log(i);
    }, i*1000 );
}

  5分钟后。。。

for(var i=1; i<=5; i++){
    (function(i){
        setTimeout(function timer(){
            console.log(i)
        }, i*1000);
    })(i);
}        
相关文章
相关标签/搜索