20.闭包、定时器

问题

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

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。

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


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容

  • 问答 什么是闭包? 有什么作用答:“官方”的解释是:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一...
    饥人谷_桶饭阅读 216评论 0 0
  • 问题 1. 什么是闭包? 有什么作用? 概念:闭包就是能够读取其他函数内部变量的函数。由于在Javascript语...
    小木子2016阅读 323评论 0 0
  • 1.什么是闭包? 有什么作用? 闭包的英文单词是closure,是指有权访问另一个函数作用域中变量的函数。 闭包在...
    GarenWang阅读 372评论 0 0
  • 什么是闭包? 有什么作用?一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达...
    饥人谷_姜琼君阅读 339评论 0 0
  • 问题 什么是闭包? 有什么作用答案: 闭包是指有权限访问另一个函数作用域的变量的函数,创建闭包的常见方式就是在一个...
    嘿菠萝阅读 405评论 0 0