20.闭包、定时器

2016-09-28  本文已影响49人  鸿鹄飞天

问题

一、什么是闭包? 有什么作用?

1.什么是闭包
①JavaScript高级程序设计第三版定义闭包是指有权访问另一个函数作用域中的变量的函数。在javascript中,只有函数内部的子函数才能读取局部变量,所以可以把闭包理解为定义在一个函数内部的函数
②内部函数在包含它们的外部函数之外被调用时,就会形成闭包,而且只要存在调用内部函数的可能,JavaScript就需要保留被引用的函数。
例如:

function f1() { 
 var n = 999; 
 function f2() {  
   console.log(n); // 999 
 }
}

上面代码中,函数f2就在函数f1内部,这时f1内部的局部变量,对f2都是可见的。但是f2内部的局部变量,对f1就是不可见的。这就是JavaScript语言的”链式作用域(chain scope)”结构,也就是子对象会一级一级地向上寻找父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
因为f2可以读取f1的局部变量,所以只要把f2作为返回值,我们就可以在f1外部读取它的内部变量。

function f1() { 
    var n = 999; 
    function f2() { 
      console.log(n); 
    } 
    return f2;
}
var result = f1();
result(); // 999

上面代码中,函数f1的返回值就是函数f2,由于f2可以读取f1的内部变量,所以就可以在f1外部获得f1的内部变量。闭包就是函数f2,即能够读取其他函数内部变量的函数。由于在JavaScript语言中,只有函数内部的子函数才能读取它的内部变量,因此可以把闭包简单理解成定义在一个函数内部的函数。闭包最大的特点,就是它可以记住诞生的环境,比如f2记住了它诞生的环境f1,所以从f2可以得到f1的内部变量。在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁

2.闭包的作用
①读取函数内部的变量,是沟通函数内部和外部的桥梁;
②闭包可以使得它诞生环境一直存在。也就是父函数的变量被子函数引用,因此内存不被释放,让这些变量的值始终保持在内存中,可能会导致内存泄露。

参考
闭包
闭包

二、setTimeout 0有什么作用?

1.setTimeout是延时函数,用来指定某个函数或某段代码,在多少毫秒之后执行。它返回一个整数,表示定时器的编号,以后可以用来取消这个定时器。

var timerId = setTimeout(func|code, delay)

上面代码中,setTimeout函数接受两个参数,第一个参数func|code是将要推迟执行的函数名或者一段代码,第二个参数delay是推迟执行的毫秒数。
注意:推迟执行的代码必须以字符串的形式,放入setTimeout,因为引擎内部使用eval函数,将字符串转为代码。如果推迟执行的是函数,则可以直接将函数名,放入setTimeout。一方面eval函数有安全顾虑,另一方面为了便于JavaScript引擎优化代码,setTimeout方法一般总是采用函数名的形式

setTimeout('console.log(2)',1000);

function f(){
  console.log(2);
}
setTimeout(f,1000);
或者
setTimeout(function (){
  console.log(2)
},1000);

2.setTimeout 0的作用:
①使setTimeout内的函数在所有要执js语句执行完成之后再执行,实现javascript的异步
例如:

console.log(1)
setTimeout('console.log(2)',0)
console.log(3)

打印结果


Paste_Image.png

②setTimeout(f, 0)可以调整事件的发生顺序。
例如:等事件完成后执行


Paste_Image.png

参考
定时器timer

代码

一、下面的代码输出多少?修改代码让fnArri输出 i。使用两种以上的方法。

var fnArr = [];
for (var i = 0; i < 10; i ++) {
    fnArr[i] =  function(){
    return i;
    };
}
console.log( fnArr[3]() );  //

输出的是10
原因:js的运行顺序是首先声明一个变量定义成数组,然后执行for循环,同时将函数赋给fnArr[i],而fnArr[i]指的是数组里面的每一项,就相当于将函数赋给数组里面的每一项。然后再执行fnArr[3](),也就是调用这个函数,return出i。但在for循环时i就已经变成了10,因为函数return出来i是要等到函数执行的时候,for循环在函数的前面就执行了。还有只有for循环完成之后输出i的一个结果,后面执行函数的时候才能return出i的值。所以这里无论i是多少函数return出来的都是10。


Paste_Image.png

1.1、立即执行函数内赋值,且用临时变量保存i的值

var fnArr = [];
for(var i=0;i<10;i++){
    (function (){ //这个立即执行函数相当于生成了10个闭包,每个闭包保存一个变量i
        var n = i;  //这里临时变量保存i的值
        fnArr[n]=function(){
            return n;
        }
    })();
}
console.log( fnArr[3]() );

1.2、立即执行函数内赋值,父函数传参

var fnArr = [];
for(var i=0;i<10;i++){
    (function (n){
        //var n = i 就相当于声明了一个临时变量,保存i
        fnArr[n]=function(){
            return n;
        }
    })(i);
}
console.log( fnArr[3]() );

2.1、整个立即执行函数被赋值,且用临时变量保存i的值

var fnArr = [];
for(var i=0;i<10;i++){
        fnArr[i]=(function(){
            var n=i;
            return function(){
                return n;
            }
        })();
}
console.log( fnArr[3]() );

2.2、整个立即执行函数被赋值,父函数需要传参

var fnArr = [];
for(var i=0;i<10;i++){
        fnArr[i]=(function(n){
            return function(){
                return n;
            }
        })(i);
}
console.log( fnArr[3]() );

3.1、可以把i绑定在函数的属性上作为函数属性的一个值,得到函数就能得到值(这个很少用)

var fnArr = [];
for(var i=0;i<10;i++){
    var fn = function(){};
    fn.idx = i; //函数身上可以绑定序号的,把这i直接绑在函数本身身上作为函数属性的值
    fnArr[i] = fn;
//i作为函数属性的一个值,得到这个函数就能得到这个值
//再将函数赋给数组,就可以通过数组获得这个值
}
console.log( fnArr[4].idx );//数组可以直接通过索引idx获取i值
Paste_Image.png

二、使用闭包封装一个汽车对象,可以通过如下方式获取汽车状态。

var Car = //todo;
Car.setSpeed(30);
Car.getSpeed(); //30
Car.accelerate();
Car.getSpeed(); //40;
Car.decelerate();
Car.decelerate();
Car.getSpeed(); //20
Car.getStatus(); // 'running';
Car.decelerate(); 
Car.decelerate();
Car.getStatus();  //'stop';
//Car.speed;  //error
var Car = (function(){
    var speed = 0;
    function setSpeed(num){
        speed = num;
    }
    function getSpeed(){
        console.log(speed);
    }
    function accelerate(){
        speed +=10;
    }
    function decelerate(){
        speed -=10;
    }
    function getStatus(){
        if(speed>0){
            console.log('running');
        }else{
            console.log('stop')
        }
    }
    return {
        setSpeed:setSpeed,
        getSpeed:getSpeed,
        accelerate:accelerate,
        decelerate:decelerate,
        getStatus:getStatus
    }
}());

Car.setSpeed(30);
Car.getSpeed(); //30
Car.accelerate();
Car.getSpeed(); //40;
Car.decelerate();
Car.decelerate();
Car.getSpeed(); //20
Car.getStatus(); // 'running';
Car.decelerate(); 
Car.decelerate();
Car.getStatus();  //'stop';
//Car.speed;  //error
Paste_Image.png

三、写一个函数使用setTimeout模拟setInterval的功能。

1.setInterval计时函数的用法与setTimeout完全一致,区别仅仅在于setInterval指定某个任务每隔一段时间就执行一次,也就是无限次的定时执行。

2.setTimeout模拟setInterval

var i=0;
function intv(){
  setTimeout(function(){
      console.log(i++);
      intv();  //注意在setTimeout函数内,对外层函数进行递归
  },1000)
};

还可以这样

var i = 0;
var b = setTimeout(function(){
    console.log(i++);
    setTimeout(arguments.callee,5000)
},2000);

四、写一个函数,计算setTimeout平均[备注:新加]最小时间粒度。

function getMin() {
    var i = 0;
    var start = Date.now()  //获取当前时间
    var clock = setTimeout(function() { 
           i++;
           if(i === 1000) {   //当i执行到1000时,延时器停止,然后获取结束时间。最小时间粒度就是最后的时间减去开始的时间除以执行的次数
               clearTimeout(clock);
               var end = Date.now();
               console.log((end - start)/i)  //end-start是整个的执行时间,i就是调用了多少次
          }

          clock = setTimeout(arguments.callee,0); //arguments.callee就是这个匿名函数,如果i没到1000的话,就再立即执行一遍这函数,这个0也属于最小时间粒度

    },0)
}
getMin()

打印结果:


Paste_Image.png

五、下面这段代码输出结果是? 为什么?

var a = 1;  //变量提前,然后把a赋值为1

setTimeout(function(){ //因为是延迟函数且延迟0,所以在所有代码最后执行
    a = 2; 
    console.log(a); //最后才开始执行到这里,所以a为2
}, 0);

var a ;  //变量提升,上面a赋值为1,所以这里a也是1
console.log(a);  //所以a为1

a = 3;  //a赋值为3
console.log(a); //所以这里a为3

打印结果


Paste_Image.png

六、下面这段代码输出结果是? 为什么?

var flag = true;
setTimeout(function(){  //关键点延迟函数最后执行
    flag = false;
},0)
while(flag){}  //执行while循环,因为flag为ture,所以循环一直存在,执行空语句,后面的代码就没法执行
console.log(flag); //没有输出

七、下面这段代码输出?如何输出delayer: 0, delayer:1...(使用闭包来实现)

for(var i=0;i<5;i++){
    setTimeout(function(){
         console.log('delayer:' + i ); 
//设置setTimeout,但是里面的函数是在最后执行,此时i已经为5。
//for每循环一次,setTimeout都要执行一次,只不过它是等到所有的代码执行完了再执行,在这里就是for循环的遍历。
//所以最后一共输出五个delayer: 5(分别是i等于0,1,2,3,4的输出),但是每次输入值为5。
    }, 0);
    console.log(i); //执行console.log,输出结果为0,1,2,3,4
}

打印结果


Paste_Image.png

这和上面第一题思路差不多:

for(var i=0;i<5;i++){
    (function(){
        var n = i; //因为i赋值给n,所以可以console.log(n)
        setTimeout(function(){
            console.log('delayer:' + n)
        },0)
        console.log(n);
    })();
}
for(var i=0;i<5;i++){
    (function(n){
        setTimeout(function(){
            console.log('delayer:' + n)
        },0)
        console.log(n);
    })(i);
}

这上面两种方法非常相同,就是在立即执行函数里面去声明变量,然后保存i

for(var i=0;i<5;i++){
        setTimeout((function(){
            var n = i;
            return function(){
               console.log('delayer:' + n) 
            };
        })(),0)
        console.log(i);
}
for(var i=0;i<5;i++){
        setTimeout((function(n){
            return function(){
               console.log('delayer:' + n) 
            };
        })(i),0)
        console.log(i);
}

上面两种方法也差不多相同,此时立即执行函数放在延迟函数里面了,也是声明一个变量,然后去保存i。

总结:闭包就是函数里面嵌套函数,关键点是理解函数有作用域链和内存不被释放的两个概念,所以可以通过暴露里面嵌套的函数,在函数外部去利用暴露出来的嵌套的函数,然后调用它。就可以访问父函数里面的变量。这是我学完这个知识点和做作业中的个人体会。


上一篇下一篇

猜你喜欢

热点阅读