资讯专栏INFORMATION COLUMN

javaScript作用域与闭包

Reducto / 1313人阅读

摘要:闭包里面保存的变量只有被方法引用了的变量这个例子里,闭包里只有并没有。那最后来说说的问题闭包到底是什么闭包是一个作用域。鉴于在的调试窗口,是放在下面的那闭包这个作用域是个什么范围被后代方法子方法,孙子方法。。。

首先给js的作用域这个话题打标签:2,var, 全局变量,局部变量,函数,undefined, 作用域提升,赋值不会提升,ReferenceError, 同名覆盖。
打完标签之后,我们来说跟作用域有关的几条铁打的规则:
1: JS的作用域有2种:全局作用域,函数作用域。

把作用域想象成一个房间,而{}是房间的门。门上装了一个猫眼,所以房间里面可以看清楚外面,但是外面却看不见里面。
在JAVA或者C里面,大括号可能会出现的情况有两种:
    1: 一个function定义的时候
    2: 一个块定义(比如if,for, while)的时候.
所以此时的作用域有三种类型:
    1: 全局作用域,
    2: 函数作用域
    3: 块级作用域。

但是在JS里面,虽然{}出现的情况也有两种,但是只有function的{}才起到栅栏的作用。

2: 声明在全局作用域里的变量是全局变量,声明在函数里面的变量是局部变量

3: 怎么创造一个全局变量和局部变量?

创造全局变量的方法有两种:

   1: 在全局作用域内用var定义: var a;
   2: 声明一个变量,不带var(无论是在全局,在函数里面还是在一个块里面):b

创造局部变量的方法只有一种:

   在函数体里面,带var声明一个变量: function func(){var b;}

3: 使用一个没有声明过的变量,会得到一个ReferenceError。无论什么情况下。
4: 不同作用域内,同名的变量,越小的作用域的变量会覆盖越大的作用域的。

4: 作用域提升:变量在声明之前就可以引用了!
这个不是和第三点矛盾了吗?其实并没有。它背后的真正原理是:并不是作用域被提升(我们前面说了,一个变量的作用域会被框在一对栅栏{}里面,一旦这个栅栏确定了,那这个作用域是不可能变化的),其实是变量的‘声明’在其作用域里面被放到任何代码之前(当然包括引用它的代码之前)。看一段代码:

var scope = "global";
function func(){
    console.log(scope); //输出‘undefined’,而不是‘global’
    var scope = "local";
    console.log(scope); //输出‘local’
}

看到第一个console,可能以为会输出‘global’, 因为通过猫眼可以看见外面的变量。但是,一旦我们进到一个函数体里面,遇到任何的变量的引用,首先要先在当前的房间里面找,只有在当前的房间里面找不到时,才到父层去找。那为什么是‘undefined呢?其实以上的代码等价于:

var scope = "global";
function func(){
    var scope; //变量的声明会提升到最前面,但是赋值并不会,所以此刻scope的值还只是undefined.
    console.log(scope); //输出‘undefined’,而不是‘global’
    scope = "local"; //赋值在这里完成
    console.log(scope); //输出‘local’
}

============闭包的分割线===========
1: 什么是闭包?
我看过看多不同的书,对必包的定义都不一样,而且就算是我知道了闭包的定义,对我真正理解它的工作原理还是没有什么用。所以,我就不去纠结它到底是什么,我接下来只关注它是怎么工作的。
2: 什么时候会形成闭包?
以下内容非原创,这里只是我自己的一个学习笔记。我看了http://www.jianshu.com/p/7312...(在这里感谢作者),跟着文章里面的例子(代码根据自己的喜好改了一些)走一遍:
1: When? 闭包出现的时刻?

function foo() {
    var a = 2;
    function baz() { 
        console.log( a );
    }
    return baz;
}
var fn = foo()
fn();//2

在断点的过程中,运行完第9行代码的时候,调试窗口里并没有出现任何闭包;直到我运行了第10行代码,跳到第5行的时候(也就是baz这个方法被调用的时候),调试窗口出现了闭包(Closure)。并且可以看到说foo是closure, 它包含一个变量a,值为2。

结论1:虽然很多书上说闭包跟函数定义的时候的作用域有关,跟它执行时候的作用域无关,但是它在浏览器里面出现的时机却是在执行的时候。

2 How? 闭包出现的条件?

function foo() {
    var a = 2;
    function baz(m) { 
        console.log(m);
    }
    return baz;
}
var fn = foo()
fn(20); //20

很多地方都说在方法里面定义方法就会形成闭包,但是在这里例子里面,我执行完第10行的代码,调试窗口并没有出现任何闭包。和例1的差别在于,baz没有引用变量a.

结论2:一个方法,一定要引用其父层方法(非自己方法内部的变量)的变量才会形成闭包。

但是闭包的形成是不是一定要执行到引用了父层作用域变量的方法才出现呢?看下面一个例子:

3: 当父层方法里有不只一个方法

function foo() {
    var a = 10;
    var b = 30;

    function fn1() {
        return a;
    }

    function fn2() {
        return 20;
    }

    return fn2;
}

var fn = foo();
fn();



当我执行到17行然后跳进其方法体(第10行)的时候,调试窗口出现了Closure, 并且它有一个变量a,
值为10。其实这个例子说明了两个事情:

结论3: 在执行一个定义在方法里面的方法时,即使它的方法体自己没有引用父层变量,但是只要有任何兄弟方法引用了,那就会形成闭包。闭包里面保存的变量只有被方法引用了的变量(这个例子里,闭包里只有a,并没有b)。
4: 当不是兄弟方法,而是子方法引用了父级变量,会发生什么情况呢?

function foo() {
    var a = 10;
    var b = 30;

    function fn1() {
        var c = 20;
        function fn2(){
            return a;
        }
        return fn2;
    }

    return fn1;
}

var fn1 = foo();
var fn2 = fn1();
fn2();

fn1方法体内没有引用任何的父层变量,但是它的子方法fn2引用了变量a。在调试的过程中,当执行到f1的时候(执行到第6行,这时候可以看到变量c都还是"undefined"),Closure就已经出现了,并不像之前那样一定要等到执行fn2.最后看一个例子:

5: 当不是直系子方法,而是侄子方法引用了父级变量

function foo() {
    var a = 10;
    var b = 30;

    function fn1() {
        var c = 20;
        function fn2(){
            return a;
        }
        return fn2;
    }
    function fn3(){
        return 40;
    }

    return fn3;
}
    
var fn3 = foo();
fn3();
    

当执行到调用fn3的时候,Chosure出现了。所以闭包到底是在什么时候形成呢?在我们前面的结论里面有提到说方法执行的时候,但是经过后面的这几个例子说明并不一定非得是执行的时候,而且JavaScript是基于词法作用域的语言(变量的作用域在其定义的时候就已经确定了,不依赖于执行时的环境),所以我倾向于闭包(Closure)是在方法定义的时候就形成了的。那最后来说说What的问题:闭包到底是什么?
1: 闭包是一个作用域。(鉴于在Chrome的调试窗口,Closure是放在Scope下面的)
2: 那闭包这个作用域是个什么范围:被后代方法(子方法,孙子方法。。。)所引用的变量所在的作用域(或者说这个变量所在的方法)。
3: 闭包里面有什么:只包含被后代方法所引用了的变量,并不包含这个变量的兄弟姐妹。
4: 上面的所有例子都只引用了变量,换成方法,也都是同样的结果。
真的是最后一个例子了:

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/83506.html

相关文章

  • Javascript重温OOP之作用域与闭包

    摘要:的变量作用域是基于其特有的作用域链的。需要注意的是,用创建的函数,其作用域指向全局作用域。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。 作用域 定义 在编程语言中,作用域控制着变量与参数的可见性及生命周期,它能减少名称冲突,而且提供了自动内存管理 --javascript 语言精粹 我理解的是,一个变量、函数或者成员可以在代码中访问到的范围。 js的变量作...

    JessYanCoding 评论0 收藏0
  • Javascript中的作用域与闭包

    摘要:作用域分为词法作用域和动态作用域。这样就形成了一个链式的作用域。一般情况下,当函数执行完毕时,里面的变量会被自动销毁。而能够访问到这个在的编译阶段就已经定型了词法作用域。 什么是作用域?在当前运行环境下,可以访问的变量或函数的范围。作用域分为词法作用域和动态作用域。词法作用域是在js代码编译阶段就确定下来的; 对应的,with和eval语句会产生动态作用域。 会产生新的作用域的情况: ...

    tianren124 评论0 收藏0
  • Js基础知识(三) - 作用域与闭包

    摘要:是词法作用域工作模式。使用可以将变量绑定在所在的任意作用域中通常是内部,也就是说为其声明的变量隐式的劫持了所在的块级作用域。 作用域与闭包 如何用js创建10个button标签,点击每个按钮时打印按钮对应的序号? 看到上述问题,如果你能看出来这个问题实质上是考对作用域的理解,那么恭喜你,这篇文章你可以不用看了,说明你对作用域已经理解的很透彻了,但是如果你看不出来这是一道考作用域的题目,...

    lemanli 评论0 收藏0
  • Js基础知识(三) - 作用域与闭包

    摘要:是词法作用域工作模式。使用可以将变量绑定在所在的任意作用域中通常是内部,也就是说为其声明的变量隐式的劫持了所在的块级作用域。 作用域与闭包 如何用js创建10个button标签,点击每个按钮时打印按钮对应的序号? 看到上述问题,如果你能看出来这个问题实质上是考对作用域的理解,那么恭喜你,这篇文章你可以不用看了,说明你对作用域已经理解的很透彻了,但是如果你看不出来这是一道考作用域的题目,...

    XFLY 评论0 收藏0
  • Js基础知识(三) - 作用域与闭包

    摘要:是词法作用域工作模式。使用可以将变量绑定在所在的任意作用域中通常是内部,也就是说为其声明的变量隐式的劫持了所在的块级作用域。 作用域与闭包 如何用js创建10个button标签,点击每个按钮时打印按钮对应的序号? 看到上述问题,如果你能看出来这个问题实质上是考对作用域的理解,那么恭喜你,这篇文章你可以不用看了,说明你对作用域已经理解的很透彻了,但是如果你看不出来这是一道考作用域的题目,...

    tanglijun 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<