|
技术交流
文章类型: |
逻辑思维 |
涉及领域: |
Javascript |
内容难度: |
噩梦 |
先来看看一个js中讲作用域最经典的代码片段
for(var i=1;i<=3;i++){undefined
setTimeout(function(){undefined
console.log(i);
},i*1000)
},
每个初学者第一反应一定以及肯定是输出1,2,3,即1s->1,2s->2,3s->3,
but,sorry 请看正确答案
1s->4,2s->4,3s->4
卧槽(你们是不是卧槽了一下),输出的是循环结束的最终值。
/******(引用《你不知道的javascript》)********/
仔细想一想,延迟函数的回调会在循环结束时才执行,
当代码改成
for(var i=1;i<=3;i++){undefined
setTimeout(function(){undefined
console.log(i);
},0)
},
输出的还是3个4,所以可以看出循环中的settimeout函数都是在循环结束后才执行的。
这里引申出一个更深入的问题,代码中到底有什么缺陷导致它的行为同语义不一致呢?
我们在之前一直以为每次循环都会绑定唯一值i,所以每次输出i的值肯定不一样,但是其实他们都共享了一个i(一个共享的全局作用域)。
这时候你会想:但是我们要的是绑定唯一值啊,咋就共享了呢,可不可以分为3个独立的作用域?
这时候我来帮你想想把******************
IIFE听过没,他全名叫做立即执行函数,IIFE会通过声明并立即执行一个函数来创建作用域。
你:说这么多干啥,试试看呀!
我们来试一下:(让你看的舒服点我截图给你看)
结果:
你:舒服有啥用哦,还不是原来的错误,没用啊你这个方法。。。。
额,等等再骂我,给我点时间解释,我们现在看起来有很多词法作用域了,的确每个延迟函数都会将IIFE再每次迭代中创建的作用域封装起来。
但是这样在setTimeout中的变量i还是外部的共享变量i,所以我们要在立即执行函数内部存在每次循环的i,
看图:
结果:
你:卧槽(占时不骂你了),好像成功了!,但是这好像好不习惯,IIFE我都不怎么用的啊,就没有别的办法了吗?
有的,不卖关子了,直接上代码:
结果:
let是es6新引入的关键字,它的作用是将变量绑定到作用域中,所以说当你不能确定是否存在共享作用域时的情况时(并且你也不希望共享时),可以直接将var替换成let。
你:直接把for循环里的var换成let不行吗?
你说的对,这样是最简单明了的,
结果:
另外当然还有方法啦,引用IT前出塞的文章:https://baijiahao.baidu.com/s?id ... r=spider&for=pc
(3)利用ES 5引入的bind函数
for (var i=1; i<=3; i++) {undefined
setTimeout( function timer(i) {undefined
console.log(i);
}.bind(null,i), i*1000 );
}
注:{}.bind(null,i)表示将i传给{}作用域中,null是因为我们并不关心硬绑定的this是什么
(4)利用setTimeout第三个参数
for (var i=1; i<=3; i++) {undefined
setTimeout( function timer(i) {undefined
console.log(i);
}, i*1000,i );
}
注:第三个参数的意义就是将i传给函数,(setTimeout函数第三个参数及以后的参数都可以作为timer函数的参数)。
(5)把setTimeout用一个方法单独出来形成闭包
var loop = function (i) {undefined
setTimeout(function timer() {undefined
console.log(i);
}, i*1000);
};
for (var i = 1;i <= 3; i++) {undefined
loop(i);
}
注:闭包就是在本身词法作用域以外的地方去调用它时,它可以记住并访问所在的词法作用域 。
总结:记住一点,for()循环是虚假的块作用域(实际上就不是块作用域)所以内部定义的变量i实质上是全局变量所以被共享了。
if()也不是块作用域。常见语言java和c里面for和if中定义的变量都是局部变量,但在js中是全局变量
|
|