资讯专栏INFORMATION COLUMN

聊一下JS中的作用域scope和闭包closure

Alliot / 1431人阅读

摘要:严格的说,在也存在块级作用域。如果多带带调用函数,比如,此时,该函数的指向全局对象,也就是。坑爹啊小明在方法内部一开始就捕获用而不是小明指向参数为空另一个与类似的方法是,唯一区别是把参数打包成再传入把参数按顺序传入。

先上几道面试题练练手
    var bb = 1;
    function aa(bb) {
      bb = 2;
      alert(bb);
    }
    aa(bb);
    alert(bb);
    var a="undefined";
    var b="false";
    var c="";
    function assert(aVar){
        if(aVar)     
            alert(true);
        else  
            alert(false);
    }
    assert(a);
    assert(b);
    assert(c);
    function Foo() {
        var i = 0;
        return function() {
            console.log(i++);
        };
    }
    Foo();
    var f1 = Foo(), f2 = Foo();
    
    f1();
    f1();
    f2();
    var foo = true;
    if (foo) {
        let bar = foo * 2;
        bar = something( bar );
        console.log( bar );
    }
    console.log( bar );
    var foo = true;
    if (foo) {
        var a = 2;
        const b = 3; //仅存在于if的{}内
        a = 3;
        b = 4; // 出错,值不能修改
    }
    console.log( a ); // 3
    console.log( b ); // ReferenceError!
闭包的深度递进

在JavaScript中,作用域是基于函数来界定的。也就是说属于一个函数内部的代码,函数内部以及内部嵌套的代码都可以访问函数的变量。

顺便讲讲常见的两种error,ReferenceError和TypeError。如上图,如果在bar里使用了d,那么经过查询都没查到,那么就会报一个ReferenceError;如果bar里使用了b,但是没有正确引用,如b.abc(),这会导致TypeError。

严格的说,在JavaScript也存在块级作用域。如下面几种情况:

with

    var obj = {a: 2, b: 2, c: 2};
    with (obj) { //均作用于obj上
      a = 5;
      b = 5;
      c = 5;  
    }

let

let是ES6新增的定义变量的方法,其定义的变量仅存在于最近的{}之内。如下

    var foo = true;
    if (foo) {
        let bar = foo * 2;
        bar = something( bar );
        console.log( bar );
    }
    console.log( bar ); // ReferenceError

const

与let一样,唯一不同的是const定义的变量值不能修改。如下:

    var foo = true;
    if (foo) {
      var a = 2;
      const b = 3; //仅存在于if的{}内
      a = 3;
      b = 4; // 出错,值不能修改
    }
    console.log( a ); // 3
    console.log( b ); // ReferenceError!

了解这些了后,我们来聊聊闭包。什么叫闭包?简单的说就是一个函数内嵌套另一个函数,这就会形成一个闭包。这样说起来可能比较抽象,那么我们就举例说明。但是在距离之前,我们再复习下这句话,来,跟着大声读一遍,“无论函数是在哪里调用,也无论函数是如何调用的,其确定的词法作用域永远都是在函数被声明的时候确定下来的”。
来,下面我们看一个经典的闭包的例子:

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

运行的结果是啥捏?你可能期待每隔一秒出来1、2、3...10。那么试一下,按F12,打开console,将代码粘贴,回车!咦???等一下,擦擦眼睛,怎么会运行了10次10捏?这是肿么回事呢?咋眼睛还不好使了呢?不要着急,等我给你忽悠!
现在,再看看上面的代码,由于setTimeout是异步的,那么在真正的1000ms结束前,其实10次循环都已经结束了。我们可以将代码分成两部分分成两部分,一部分处理i++,另一部分处理setTimeout函数。那么上面的代码等同于下面的:

    // 第一个部分
    i++; 
    i++; // 总共做10次
              
    // 第二个部分
    setTimeout(function() {
        console.log(i);
    }, 1000);
    
    setTimeout(function() {
       console.log(i);
    }, 1000); // 总共做10次

看到这里,相信你已经明白了为什么是上面的运行结果了吧。那么,我们来找找如何解决这个问题,让它运行如我们所料!

因为setTimeout中的匿名函数没有将i作为参数传入来固定这个变量的值,让其保留下来, 而是直接引用了外部作用域中的i, 因此i变化时,也影响到了匿名函数。其实要让它运行的跟我们料想的一样很简单,只需要将setTimeout函数定义在一个多带带的作用域里并将i传进来即可。如下:

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

不要激动,勇敢的去试一下,结果肯定如你所料。那么再看一个实现方案:

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

啊,居然这么简单啊,你肯定在这么想了!那么,看一个更优雅的实现方案:

    for (let i=1; i<=9; i++) {
         setTimeout( function timer(){
             console.log( i );
         }, 1000 );
     }

咦?!肿么回事呢?是不是出错了,不着急,我这里也出错了。这是因为let需要在strict mode中执行。具体如何使用strict mode模式,自行谷歌吧

再整理一些面试题吧
    var x = 1;
    var y = 0;
    var z = 0;
    function add(n){n=n+1;}
    y = add(x);
    function add(n){n=n+3;}
    z = add(x);
    console.log(x,y,z);
    //两个函数没有返回值,打印1 undefined undefined
    function getAge() {
        var y = new Date().getFullYear();
        return y - this.birth;
    }
    
    var xiaoming = {
        name: "小明",
        birth: 1990,
        age: getAge
    };
    
    xiaoming.age(); // 25, 正常结果
    getAge(); // NaN

多带带调用函数getAge怎么返回了NaN?请注意,我们已经进入到了JavaScript的一个大坑里。JavaScript的函数内部如果调用了this,那么这个this到底指向谁?

答案是,视情况而定!如果以对象的方法形式调用,比如xiaoming.age(),该函数的this指向被调用的对象,也就是xiaoming,这是符合我们预期的。

如果多带带调用函数,比如getAge(),此时,该函数的this指向全局对象,也就是window。
坑爹啊!

    var xiaoming = {
        name: "小明",
        birth: 1990,
        age: function () {
            var that = this; // 在方法内部一开始就捕获this
            function getAgeFromBirth() {
                var y = new Date().getFullYear();
                return y - that.birth; // 用that而不是this
            }
            return getAgeFromBirth();
        }
    };
    
    xiaoming.age(); // 25
    function getAge() {
        var y = new Date().getFullYear();
        return y - this.birth;
    }
    
    var xiaoming = {
        name: "小明",
        birth: 1990,
        age: getAge
    };
    
    xiaoming.age(); // 25
    getAge.apply(xiaoming, []); // 25, this指向xiaoming, 参数为空

另一个与apply()类似的方法是call(),唯一区别是:

apply()把参数打包成Array再传入;

call()把参数按顺序传入。

    function foo() {
        var x = "Hello, " + y;
        alert(x);//hello,undefined
        var y = "Bob";
    }
    foo();

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

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

相关文章

  • JavaScript中的闭包

    摘要:闭包引起的内存泄漏总结从理论的角度将由于作用域链的特性中所有函数都是闭包但是从应用的角度来说只有当函数以返回值返回或者当函数以参数形式使用或者当函数中自由变量在函数外被引用时才能成为明确意义上的闭包。 文章同步到github js的闭包概念几乎是任何面试官都会问的问题,最近把闭包这块的概念梳理了一下,记录成以下文章。 什么是闭包 我先列出一些官方及经典书籍等书中给出的概念,这些概念虽然...

    HmyBmny 评论0 收藏0
  • JavaScript基础系列---闭包及其应用

    摘要:所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。所以本文中将以维基百科中的定义为准即在计算机科学中,闭包,又称词法闭包或函数闭包,是引用了自由变量的函数。 闭包(closure)是JavaScript中一个神秘的概念,许多人都对它难以理解,我也一直处于似懂非懂的状态,前几天深入了解了一下执行环境以及作用域链,可戳查看详情,而闭包与作用域及作用域链的关系密不可分,所...

    leoperfect 评论0 收藏0
  • 老生常谈之闭包(你不可不知的若干知识点)

    摘要:闭包是什么这是一个在面试的过程中出现的概率为以上的问题,也是我们张口就来的问题。文章推荐我们面试中在被问到闭包这个问题是要注意的几点闭包的延伸,让面试变得 闭包是什么?这是一个在面试的过程中出现的概率为60%以上的问题,也是我们张口就来的问题。但是我们往往发现,在面试的过程中我们的回答并不那么让面试官满意,我们虽然能张口说出一些但是却不能系统的对这个问题进行回答。面试官希望加入自己团队...

    daydream 评论0 收藏0
  • 【闯关模式】作用、链闭包

    摘要:前言这个系列是翻译自中的直接闯关作用域链和闭包作用域,作用域链,闭包和垃圾回收机制都有一个共同点学了就忘闭包到底是干啥的啥时候发生垃圾回收机制作用域链到底是啥这个教程让你发现这些都是小意思。 前言 这个系列是翻译自 nodeschool.io中的 scope-chains-closures 直接闯关: npm install -g scope-chains-closures scope...

    shinezejian 评论0 收藏0
  • javaScript作用闭包

    摘要:闭包里面保存的变量只有被方法引用了的变量这个例子里,闭包里只有并没有。那最后来说说的问题闭包到底是什么闭包是一个作用域。鉴于在的调试窗口,是放在下面的那闭包这个作用域是个什么范围被后代方法子方法,孙子方法。。。 首先给js的作用域这个话题打标签:2,var, 全局变量,局部变量,函数,undefined, 作用域提升,赋值不会提升,ReferenceError, 同名覆盖。打完标签之后...

    Reducto 评论0 收藏0

发表评论

0条评论

Alliot

|高级讲师

TA的文章

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