经典问题:for循环中的定时器
/**
* 观察以下代码片段,提出修改方案
*/
for(var i = 0; i < 10; i++) {
setTimeout(function(){
console.log(i);
}, 100);
}
解答
/**************方案一********************/
for(let i = 0; i < 10; i++) {
setTimeout(function(){
console.log(i);
}, 100);
}
/**************方案二*********************/
for(var i = 0; i < 10; i++) {
setTimeout(function(num){
console.log(num);
}, 100, i);
}
/**************方案三(耍赖(***************/
for(var i = 0; i < 10; i++) {
+function(){
console.log(i);
}()
}
-
方案一解析
通过ES6的
let关键字,管理i的作用域,每次观察i时都是观察的定时器启动时i的数值。块级作用域立功!通过块级作用域形成闭包 -
方案二解析
通过setTimeout的API解决,每次执行时,都传入参数i,并通过形参形成闭包。熟记API的胜利
-
方案三解析
这还解析啥,其实,还是能解析一下的,也就是事件循环机制
/**
* 观察以下代码片段
*/
for(var i = 0; i < 10; i++) {
setTimeout(function(){
console.log(i);
}, 0);
}
如果你不了解事件循环或者不了解同步异步,那么有可能这里你会认为能够正常输出
首先,这里肯定还是会输出10个10的,毫无疑问。
什么是事件循环:
js是一门单线程的语言,以事件周期为单位执行代码,在事件周期内的代码按照标准顺序执行,脱离事件周期的代码在可执行的事件周期上执行。脱离事件周期的代码被称为异步代码,异步代码又分为宏任务(macro-task)和微任务(micro-task);常见的异步宏任务setTimeout** setInterval **XMLHttpRequest script加载,常见的微任务:Promise process.nextTick
概念就先了解到这里,更多内容请看事件循环
显然这里即便是setTimeout也是一个异步的宏任务,只有在for循环完全执行之后才会进行执行的,可能有的人会问了:setTimeout设置的延时是0啊,显然声明完它就可以执行了,没错,但是对于js来说,只要事件循环没有完成,那么异步代码是无法插队执行的。这些异步代码会放到一个异步执行队列里面,等待合适的实际(即之前的事件循环完成,并满足触发条件)再进行执行。
所以这里,为了打破事件循环机制,改用了IIFE(Immediately-invoked function expression)立即执行函数
闭包与链式调用
function fun(n,o) {
console.log(o)
return {
fun:function(m){
return fun(m,n);
}
};
}
var a = fun(0); a.fun(1); a.fun(2); a.fun(3);//undefined,?,?,?
var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?
var c = fun(0).fun(1); c.fun(2); c.fun(3);//undefined,?,?,?
//问:三行a,b,c的输出分别是什么?
题目来自 博客园-小小沧海的【大部分人都会做错的经典JS闭包面试题】
解答
这里先观察第一个,第一次调用fun(0) 必然打印undefined
此时a的值为
{
fun:function(m){
return fun(m,n);
}
}
其中m是待传入的参数,n是一开始传入的参数0。注意这个时候n是被闭包捕获的。
a.fun(1) m的值为1,n的值为0 fun的行为没有发生改变,打印0,调用结束并返回一个新的对象
a.fun(2)/a.fun(3)同理 答案为//undefined,0,0,0
观察第二个var b = fun(0).fun(1).fun(2).fun(3);
分步骤来看b0=fun(0)打印undefined
b1 = b0.fun(1)打印0
到这里和上一步一样,然后让我们观察一下b1的状态
n值为1,o值为0
返回的方法中m为带传入的参数,n值为被此轮闭包捕获的值1
b2 = b1.fun(2) 打印 1
同理
b = b2.fun(3) 打印2
答案为 //undefined,0,1,2
最后一个 更简单了
直接说答案 //undefined,0,1,1
利用闭包控制访问权限
观察以下代码
/**
* 实现栈结构
*/
function Stack(){
this.items = [];
this.push = item=>{
this.items.push(item);
}
this.pop = ()=>this.items.pop();
}
let stack = new Stack();
经过测试发现问题。可以通过stack.items 直接访问和修改栈内的数据,请提出修改方案
解答
思路基于JS-面向对象之封装(下) (这个我之后会发布到掘金上)
function Stack(){
const items = [];
this.push = item=>{
items.push(item); // 这里通过闭包访问item
}
this.pop = ()=>items.pop();// 这里通过闭包访问item
}
let stack = new Stack();
stack.items // undefined
简单的乘法器
按照描述实现算法
实现方法mult,可接受1~2个参数
- 若参数中不包含回调函数,则返回一个包含
mult方法的对象 - 若参数中包含回调函数,则执行回调函数
- 不需要考虑传入参数类型异常的情况
实现效果
mult(console.log) // 1
let mult4 = mult(4)
mult4(console.log) // 4
mult4.mult(7, console.log)//28
let mult28 = mult4.mult(7)
mult28(console.log) // 28
let mult2800 = mult28.mult(100)
mult2800(console.log) // 2800
multshow = mult(2, console.log) //2
// 计算10的阶乘
mult(1).mult(2).mult(3).mult(4).mult(5, console.log)//120
解答
参考本篇中的闭包与链式调用
let _mult = function(base) {
return function(param, cb) {
if('funciton' == typeof param){
cb = param;
param = 1;
}
if(cb) {
cb(base * param);
}else {
return {
mult: _mult(base * param)
}
}
}
}
let mult = _mult(1)
对闭包还有不了解的小伙伴可以参考【JavaScript-闭包】