偶然在SegmentFault上看到个链接,结果引导到知乎上了:如果你想靠前端技术还房贷,你不能连这个都不会。里面提出了两个问题:
注意,蓝色文本为完全引用知乎原文:
第一个是经典问题,考察闭包的使用:
1 2 3 4 |
//请问输出什么 for (var i = 0; i < 10; ++i) { setTimeout(function () {console.log(i)}, 0); } |
第一问,这段代码输出什么?第二问,如果想让这段代码输出0123456789,应该怎么修改?
第二个问题是口述实现,设计场景如下:
1 2 3 |
某个应用模块由文本框 input,以及按钮 A,按钮 B 组成。点击按钮 A,会向地址 urlA 发出一个 ajax 请求,并将返回的字符串填充到 input 中(覆盖 input 中原有的数据),点击按钮 B,会向地址 urlB 发出一个 ajax 请求,并将返回的字符串填充到 input 中(覆盖 input 中原有的数据)。 当用户依次点击按钮 A、B 的时候,预期的效果是 input 依次被 urlA、urlB 返回的数据填充,但是由于到 urlA 的请求返回比较慢,导致 urlB 返回的数据被 urlA 返回的数据覆盖了,与用户预期的顺序不一致。 |
解答
第一题
1 2 3 |
for (var i = 0; i < 10; ++i) { setTimeout(function () {console.log(i)}, 0); } |
第一题我以为答案是10个null,后来发现这是js不是php。
setTimeout执行的函数是在线程结束后才执行的,所以会一口气执行10个function () {console.log(i)},这时的i是循环结束时的i,所以i==10,所以输出10个10。
如果想要输出0123456789,setTimeout的时候给console.log给值0123456789,再引入个变量并执行就好了。
1 2 3 4 5 6 7 8 |
for (var i = 0; i < 10; ++i) {(function() { var j = i; setTimeout(function() { console.log(j); }, 1000); } )(); } |
不过我就纳了闷了,这道题真的要用闭包这种方法去解决吗?为什么不直接把var换成let?
1 2 3 |
for (let i = 0; i < 10; ++i) { setTimeout(function () {console.log(i)}, 0); } |
总感觉出题者意淫的厉害。
第二题
我用jquery的ajax试了一下,写了70行左右代码。当然是有优化余地的,但是没什么心情。
主要思想是flag,即最后点击的哪个按钮,就显示哪个按钮的结果,渲染的时候不由ajax.success来做,而交给一个专门刷新input的函数来做,ajax.success只处理返回的数据和触发刷新。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
var whichmsg; var result0; var result1; $(document).ready(function() { $('#btn0').on('click', api0); $('#btn1').on('click', api1); $('#btn-both').on('click', api10); }); function api0() { whichmsg = 0; $.ajax({ type: 'GET', url: 'api0.php',//会立即返回数据 async: true, success: function(msg) { console.log(msg); $('#api0').val(msg); result0 = msg; refreshFinal(); }, error: function(msg) { console.log(msg); } }); }; function api1() { whichmsg = 1; $.ajax({ type: 'GET', url: 'api1.php',//等待1秒钟后才会返回数据 async: true, success: function(msg) { console.log(msg); $('#api1').val(msg); result1 = msg; refreshFinal(); }, error: function(msg) { console.log(msg); } }); }; function api10() { api1(); api0(); }; function refreshFinal() { var msg; console.log('flag:' + whichmsg); if (whichmsg === 0) { msg = result0; } else if (whichmsg === 1) { msg = result1; } $('#final').val(msg); } |
至少这个思路简单且完全没有阻塞。下面讨论有纯阻塞的,也有队列的。感觉用队列的思路不错,不过我不知道什么时候从队列里触发刷新。我主职是linux和php,对js只了解常用的,而且对MVVM不熟。
那篇文章下面精选评论说10行代码就能搞定的,我有点纳闷。
嘛,农民们都是充满了鄙视链的,而且这毕竟是逼乎嘛。
再说了,连Git都不会用的都能买得起房,工资收入什么的跟技术没什么关系。主要还是看风大不大,跟猪重不重没什么关系。
当然了,真的会飞的猪还是很NB的!
全部代码
有考虑传到GitHub上,但是感觉自己写的太渣,再说吧。
7 comments
Skip to comment form ↓
泠
2017 年 2 月 23 日 在 上午 12:55 (UTC 8) Link to this comment
第一个问题:
console.log结尾是会换行的,我不知道上面代码是怎么可能输出0123456789的
第二个问题
既然你的b都已经比a慢了,又要显示a,直接等2个都返回结果不就行了
···
let res = await Promise.all([fetch(A),fetch(B)]);
ele.value = res[0].body;
await sleep(2000);
ele.value = res[1].body;
···
反正最重要的效果是A,B都要显示
或者就是先创建2个Promise
利用Promise.race,判断最快的是不是A,如果是b就await A, ele.val = a.body,然后再从B赋值。反之亦然
石樱灯笼
2017 年 2 月 23 日 在 上午 10:23 (UTC 8) Link to this comment
不要太纠结细节,纠结细节的话没一道题是正经题。
yolo
2017 年 5 月 4 日 在 下午 4:43 (UTC 8) Link to this comment
真的还都是比较正经的题目。类似的场景经常能遇到的。
泠
2017 年 2 月 23 日 在 上午 1:14 (UTC 8) Link to this comment
阿偶,第二题好像看错了。。
那就存一个变量记录fetching状态好了www
如果fetching = true 就 setInterval 自己,在定时的函数里检查fetching以赋值。只有A,B2个用boolean就够了
多了用个number存order…
yolo
2017 年 5 月 4 日 在 下午 4:42 (UTC 8) Link to this comment
看幽灵诡计的东西偶然看到这个文章,一个是闭包,一个是异步,都是很基础但是非常核心的内容。
第一个直接加个匿名函数就行:
for (var i = 0; i < 10; ++i) {
(function(i) {
setTimeout(function () {console.log(i)}, 0);
})(i)
}
第二题,可以实现的方式很多,比较好的处理方式是,抛开A、B、C、D,不管有多少个请求,也不管哪个请求比较快哪个请求比较慢。构造一个通用的场景,记录最后一次试图触发input修改的时间戳,当执行修改的时候验证是否一致。大概结构:
const inputObj = {
lastTrigger,
triggerChange(thisTime) {
// 在这里可以验证是否真正执行修改
}
}
shyling
2017 年 5 月 6 日 在 下午 5:18 (UTC 8) Link to this comment
对了,之前回复忘了说…
第一个和闭包没什么关系的。。只是 var i 在 for 里不会在每次 loop 自动 copy,setTimeout 的 Task 会在当前代码执行完成后执行。
闭包是指 自由变量被函数捕捉带出了作用域。
而在你的例子里
var j = i;
和 楼上的例子里的 IIFE 都是把循环的变量复制了一遍而已。唔,所以说没必要写的这么麻烦啦。
for (var i = 0; i < 10; ++i) {
setTimeout(function (i) {console.log(i)}, 0, i);
}
这个东西自己是有补自己的坑的,setTimeout(fn,delay[, …args])
石樱灯笼
2017 年 5 月 6 日 在 下午 5:35 (UTC 8) Link to this comment
额外复制一个变量其实是个习惯。因为很多人的很多代码写的都很烂,i 很有可能有在其他作用域里存在(当然这个例子里的i是干净的),额外复制一份,就可以在自己的代码块中随意修改了。
如果不这样做,很有可能会因为你的代码「本身没问题」,却在添加到项目中后其他功能就挂掉,导致最终 Bug 分配到你头上。那种全局级别的垃圾代码不是你想修就能修得了的,尽量避免引屎上身。