Posts List

从一道面试题谈谈 setTimeout 和 setInterval

最近有看到一道题目,使用 JavaScript,隔一秒打印一个数字,比如第 0 秒打印 0,第 1 秒打印 1 等等,如何去实现? 假如我们尝试使用 setTimeout 去实现: for (var i = 0; i < 5; i++) { setTimeout(function() { console.log(i); }, i * 1000); } 这样可以么,执行的结果是什么呢?你可以将这段代码粘贴到 浏览器的 Console 中运行一下。结果是,每隔一秒打印一个 5 ,一共打印 5 次。这是为什么呢,为什么不是打印 0, 1, 2, 3, 4 呢?众所周知,JavaScript 是一种单线程语言,主线程的语句和方法会阻塞定时任务的执行,在 JavaScript 执行引擎之外,存在一个任务队列。当代码中调用 setTimeout 方法时,注册的延时方法会挂在浏览器其他模块处理,等达到触发条件是,该模块再将要执行的方法添加到任务队列中。这个过程是与执行引擎主线程独立,只有在主线程方法全部执行完毕的时候,才会从该模块的任务队列中提取任务来执行。这就是为什么 setTimeout 中函数延迟执行的时间往往大于设置的时间。 因此,对于上述的代码块,每一个 setTimeout 函数都被添加到了任务队列中。然后,这还涉及到了函数作用于的问题。因为当任务队列中的函数执行的时候,其作用域其实是全局作用域。setTimeout 中的打印函数执行的时候就会在全局作用域中寻找变量 i,而此时全局作用域的变量 i 的值已经变成 5 了。这也就是为什么打印的数字都是 5。那么应该如何达到我们一开始预期的效果呢?这里我们就需要考虑到函数执行上下文的问题,可以通过立即执行函数(IIFE)来改变函数作用域。 for (var i = 0; i < 5; i++) { (function(i) { setTimeout(function() { console.log(i); }, i * 1000); })(i); } 你可以将这段代码执行一下,可以看看执行的效果,应该就可以达到预期的效果了。通过立即执行函数改变函数运行的作用域,并且将要打印的变量传入到函数参数中,如此就能打印出正确的数字了。那么除了 setTimeout,我们是不是还有其它的方法呢?答案是有的,我们可以使用 setInterval 方法。

消灭 star 大作战--Front-end-tutorial

写在前面 Github star 往往非常简单,点击一个按钮,就 star 了。但是你还去看它么,这就未必了。因此很多库长年累月的堆积在你的 star list 里面无人问津。因此,会有这样一个具有一个非常中二的名字的计划。对于 star 仓库,从后往前,一个个理解消化,不要让它无意义地堆积。 操作步骤: fork it finish it 仓库信息 仓库名称:Front-end-tutorial 主要内容:这是一个博客,里面主要是前端开发的内容,内容设计比较广泛,包括 HTML, CSS, JS 以及流行的框架,以及前端开发的其他内容。 消灭计划:内容较多,打算主要消化一些感兴趣的内容,主要应该集中于原生的东西或者一些性能方面的知识。 作战内容 JavaScript 深拷贝 深拷贝可以说是一个老生重谈的问题,几乎每一个前端面试都可能会问这样的问题。Js 中的对象都是引用,所以浅拷贝时,修改拷贝后的对象会影响原对象。原仓库中其实讲的并不是很深入,我反倒是觉得评论里面的一篇文章深入剖析 JavaScript 的深复制讲得更好。 有很多第三方库实现了对于对象的深拷贝。 jQuery: $.extend(true, {}, sourceObject) loadsh: _.clone(sourceObject, true) 或者 _.cloneDeep(sourceObject) 另外有一个神奇的方法就是借助于 JSON 的 parse 和 stringify 方法,当时我才看到这个方法的时候惊为天人,这个方法还可以用来判断两个对象是否相等。当然,这个方法还是有一些限制,因为正确处理的对象只能是使用 json 可以表示的数据结构,对于函数可能就无能为力了。原文作者实现了一个深拷贝的方法,不过考虑了很多情况,在这里我们就实现一个简单版的深拷贝把。 function deepCopy(obj) { const result = {}; for (const key in obj) { if (obj.hasOwnProperty(key)) { if (Object.prototype.toString.call(obj[key]).indexOf('Array') !== -1 || Object.prototype.toString.call(obj[key]).indexOf('Object') !== -1) { result[key] = deepCopy(obj[key]); } else { result[key] = obj[key]; } } } return result; } call 和 apply call 和 apply 应该是两个非常类似的方法,我的理解它们都是改变函数运行的作用域。不同之处就是参数不同,apply 接收两个参数,一个是函数运行的作用域,另外一个是参数数组,而 call 的第一个参数相同,后面传递的参数必须列举出来。

Mongoose中document和object的区别

这个问题其实是mongoose非常常见的问题,经常有很多以前没遇到这个问题的人都会被这个问题弄得怀疑人生。我们先介绍一些问题的背景。先看下面一段代码: router.get('/', function(req, res, next) { // res.render('index', { title: 'Express' }); const model = mongoose.model('realestate'); const queryCretia = {}; model.find(queryCretia, (err, docs) => { res.render('index', { title: 'express', docs: docs }) }) }); <!DOCTYPE html> <html> <head> <title><%= title %></title> <link rel='stylesheet' href='/stylesheets/style.css' /> </head> <body> <h1><%= title %></h1> <p>Welcome to <%= title %></p> <!-- <%= docs %> --> <ul> <% docs.forEach(function(doc){ %> <li><%= doc.type %></li> <% }) %> </ul> </body> </html> 在第一段代码中,通过model.find我们应该能够获取到根据queryCriteria获取的结果,结果应该是一个对象数组,类似于这样: [{ "_id" : ObjectId("59bdeadb2a5c612514ee7970"), "title" : "好楼层,中等装修,满5年,上门实拍", "type" : "2室1厅", "square" : "75.42平", "direction" : "朝南", "floor" : "中区/6层", "unitPrice" : 47732, "totalPrice" : 360, "location" : null, "specialExplain" : "满五", "url" : "http://sh.lianjia.com//ershoufang/sh4528035.html", "station" : "江杨北路", "line" : "3号线", "updateTime" : "2017-09-17 11:24:11" } { "_id" : ObjectId("59bdeadb2a5c612514ee7971"), "title" : "南北户型,厨卫全明,高区采光好,装修精美", "type" : "2室2厅", "square" : "90.92平", "direction" : "朝南北", "floor" : "高区/6层", "unitPrice" : 46194, "totalPrice" : 420, "location" : null, "specialExplain" : "满五", "url" : "http://sh.lianjia.com//ershoufang/sh4546221.html", "station" : "江杨北路", "line" : "3号线", "updateTime" : "2017-09-17 11:24:11" }] 预期index.ejs应该渲染的页面是一个ul渲染的结果,类似于

service worker之cache实践--sw-precache

Progressive web application是谷歌推出的一种渐进式应用,我觉得其实PWA是一种非常具有发展前景的技术。首先,PWA是由谷歌推出的,而且跨平台,PWA可以给你类似于原生APP的体验,通过service worker,你可以将资源缓存到本地。但是,PWA再国内一直都是不温不火,主要有好几个原因:一是因为国内的浏览器环境比较复杂,而PWA一般只是能够在chrome浏览器得到较好的支持。虽然chrome在桌面端占据了很大比例,但是在移动端还是一般般,普通的用户不一定会去安装Chrome。二是safari浏览器对于PWA的支持不是很完美,service worker目前还是没有得到支持的。 但是我是觉得PWA还是很好的,值得开发者们进一步探索。有一点偏题了,今天要讨论的其实是PWA里面service worker资源的缓存问题。主要问题的背景是这样的,我有一个上海地铁线路图的PWA,可以支持离线使用,有兴趣的同学可以尝试看看。我遇到一个问题,就是每次我更新之后代码之后,加入我的PWA被添加到主屏之后,这个APP的代码就没有更新,必须删除后重新重浏览器中添加到主屏。一开始我以为是PWA的问题,后来竟别人提醒,桌面上的APP其实也就是网站的链接。我这才恍然大悟,问题是因为我的servicer worker里面的缓存策略有问题。因为我的APP通过service worker来缓存资源,包括js,css以及图片文件,所以始终是从缓存中加载资源,所以我远程代码更新后,这个APP的代码却没有得到更新。OK,拿代码说话,我一开始的代码是: var cacheName = 'subway'; var filesToCache = [ '/', 'index.html', 'image/transfer.png', 'dist/alloy_finger.js', 'app.css' ]; self.addEventListener('install', function(e) { console.log('service worker install'); e.waitUntil(caches.open(cacheName).then(function(cache) { console.log('serviceworker caching app shell'); return cache.addAll(filesToCache); })); }); 可以看出我们在 install 事件后通过在 cache 里面加载文件,所以我们必须选择一种合适的策略能够让我们的APP在代码更新之后去请求新的代码呢? Google其实在PWA推出的过程中也给出了很多有用的技术。比如sw-precache以及sw-toolbox,以及最近正在发展过程中的sw-helper。这里,我主要使用的是sw-precache来更新我的service worker策略。 sw-precache也是NODE中的一个模块,可以通过npm install sw-precache来进行安装。sw-precache可以配合多个工具使用,这里我主要介绍一下如何配合gulp来使用。我们通过利用sw-precache来帮助我们生成sw-precache。饿了么的huangxuan在medium写了一篇文章来渗入地介绍sw-precache,这篇文章写的不错,但是却是在墙外,主要是介绍sw-precache的工作方式。我就谈一下我对sw-precache的理解把,以一个gulpfile的一段代码为例: gulp.task('generate-sw', function(callback) { var path = require('path'); var swPrecache = require('sw-precache'); swPrecache.write(path.join('sw.js'), { staticFileGlobs: [ 'app.js', 'dist/alloy_finger.js', 'dist/app.css', 'image/*.{png}', 'index.html', '/' ] }, callback) }) 我们通过利用 sw-precache 来生成 sw.js 文件。主要的方式是检测你在staticFileGlobs里面的文件有没有发生变化,如果发生变化就会重新生成hash码,从而能够使得APP在代码更新之后向远程请求新的代码。 这篇文章可能只是很小的一点,关于 service worker 的使用还有很多东西值得学习,欢迎关注我的 github 共同探讨。