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

先上几道面试题练练手


    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();
    原文作者:raganyaYoung
    原文地址: https://segmentfault.com/a/1190000006552384
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞