用了这么久HTTP-你是否了解ContentLength

摘要: 理解HTTP协议... 原文:用了这么久HTTP, 你是否了解Content-Length和Transfer-Encoding ?作者:朴瑞卿的博客由Content-Length导致的问题引发的一系列思考:前段时间开发API网关, 使用postman调试时出现了超时的情况, 经排查确定是请求数据被处理后Content-Length与实际不一致导致的问题, 故有此文.Content-Length, HTTP消息长度, 用十进制数字表示的八位字节的数目. 一般情况下, 很多工作都被框架完成, 我们很少去关注这部分内容, 但少数情况下发生了Content-Length与实际消息长度不一致, 程序可能会发生比较奇怪的异常, 如: 无响应直到超时.请求被截断, 而且下一个请求解析出现错乱.Content-Length是HTTP消息长度, 用十进制数字表示的八位字节的数目, 是Headers中常见的一个字段. Content-Length应该是精确的, 否则就会导致异常 (特别地, HTTP1.0中这个字段可有可无). Content-Length首部指示出报文中实体主体的字节大小. 这个大小是包含了所有内容编码的, 比如, 对文本文件进行了gzip压缩的话, Content-Length首部指的就是压缩后的大小而不是原始大小. Content-Length是如何工作的Content-Length使用十进制的数字表示了消息的长度, 服务端/客户端通过它来得知后续要读取消息的长度. 如果这个长度不正确, 会发生如下情况: Content-Length > 实际长度如果Content-Length比实际的长度大, 服务端/客户端读取到消息结尾后, 会等待下一个字节, 自然会无响应直到超时. 同样地, 在响应消息中Content-Length超过实际长度也是一样的效果: Content-Length < 实际长度如果这个长度小于实际长度, 首次请求的消息会被截取, 比如参数为param=piaoruiqing, Content-Length为10, 那么这次请求的消息会被截取为: param=piao, 如图所示: 但, 仅仅是如此吗, 当然不, 我们再来看看第二次请求会发生什么让人意外的事情, 如图: 连续的两次请求, 第一次消息被截断, 而第二次没有发生预期的截断, 而是服务端抛出了异常: Request method 'ruiqingPOST' not supported.刺不刺激 (ノ)゚゚( ) ...

September 10, 2019 · 1 min · jiezi

如何实现Web页面录屏

摘要: 很有意思的操作... 原文:web页面录屏实现译者:frontdogFundebug经授权转载,版权归原作者所有。 写在前面的话在看到评论后,突然意识到自己没有提前说明,本文可以说是一篇调研学习文,是我自己感觉可行的一套方案,后续会去读读已经开源的一些类似的代码库,补足自己遗漏的一些细节,所以大家可以当作学习文,生产环境慎用。 录屏重现错误场景如果你的应用有接入到web apm系统中,那么你可能就知道apm系统能帮你捕获到页面发生的未捕获错误,给出错误栈,帮助你定位到BUG。但是,有些时候,当你不知道用户的具体操作时,是没有办法重现这个错误的,这时候,如果有操作录屏,你就可以清楚地了解到用户的操作路径,从而复现这个BUG并且修复。 实现思路思路一:利用Canvas截图这个思路比较简单,就是利用canvas去画网页内容,比较有名的库有:html2canvas,这个库的简单原理是: 收集所有的DOM,存入一个queue中;根据zIndex按照顺序将DOM一个个通过一定规则,把DOM和其CSS样式一起画到Canvas上。这个实现是比较复杂的,但是我们可以直接使用,所以我们可以获取到我们想要的网页截图。 为了使得生成的视频较为流畅,我们一秒中需要生成大约25帧,也就是需要25张截图,思路流程图如下: 但是,这个思路有个最致命的不足:为了视频流畅,一秒中我们需要25张图,一张图300KB,当我们需要30秒的视频时,图的大小总共为220M,这么大的网络开销明显不行。 思路二:记录所有操作重现为了降低网络开销,我们换个思路,我们在最开始的页面基础上,记录下一步步操作,在我们需要"播放"的时候,按照顺序应用这些操作,这样我们就能看到页面的变化了。这个思路把鼠标操作和DOM变化分开: 鼠标变化: 监听mouseover事件,记录鼠标的clientX和clientY。重放的时候使用js画出一个假的鼠标,根据坐标记录来更改"鼠标"的位置。DOM变化: 对页面DOM进行一次全量快照。包括样式的收集、JS脚本去除,并通过一定的规则给当前的每个DOM元素标记一个id。监听所有可能对界面产生影响的事件,例如各类鼠标事件、输入事件、滚动事件、缩放事件等等,每个事件都记录参数和目标元素,目标元素可以是刚才记录的id,这样的每一次变化事件可以记录为一次增量的快照。将一定量的快照发送给后端。在后台根据快照和操作链进行播放。当然这个说明是比较简略的,鼠标的记录比较简单,我们不展开讲,主要说明一下DOM监控的实现思路。 页面首次全量快照首先你可能会想到,要实现页面全量快照,可以直接使用outerHTML const content = document.documentElement.outerHTML;这样就简单记录了页面的所有DOM,你只需要首先给DOM增加标记id,然后得到outerHTML,然后去除JS脚本。 但是,这里有个问题,使用outerHTML记录的DOM会将把临近的两个TextNode合并为一个节点,而我们后续监控DOM变化时会使用MutationObserver,此时你需要大量的处理来兼容这种TextNode的合并,不然你在还原操作的时候无法定位到操作的目标节点。 那么,我们有办法保持页面DOM的原有结构吗? 答案是肯定的,在这里我们使用Virtual DOM来记录DOM结构,把documentElement变成Virtual DOM,记录下来,后面还原的时候重新生成DOM即可。 DOM转化为Virtual DOM我们在这里只需要关心两种Node类型:Node.TEXT_NODE和Node.ELEMENT_NODE。同时,要注意,SVG和SVG子元素的创建需要使用API:createElementNS,所以,我们在记录Virtual DOM的时候,需要注意namespace的记录,上代码: const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';const XML_NAMESPACES = ['xmlns', 'xmlns:svg', 'xmlns:xlink'];function createVirtualDom(element, isSVG = false) { switch (element.nodeType) { case Node.TEXT_NODE: return createVirtualText(element); case Node.ELEMENT_NODE: return createVirtualElement(element, isSVG || element.tagName.toLowerCase() === 'svg'); default: return null; }}function createVirtualText(element) { const vText = { text: element.nodeValue, type: 'VirtualText', }; if (typeof element.__flow !== 'undefined') { vText.__flow = element.__flow; } return vText;}function createVirtualElement(element, isSVG = false) { const tagName = element.tagName.toLowerCase(); const children = getNodeChildren(element, isSVG); const { attr, namespace } = getNodeAttributes(element, isSVG); const vElement = { tagName, type: 'VirtualElement', children, attributes: attr, namespace, }; if (typeof element.__flow !== 'undefined') { vElement.__flow = element.__flow; } return vElement;}function getNodeChildren(element, isSVG = false) { const childNodes = element.childNodes ? [...element.childNodes] : []; const children = []; childNodes.forEach((cnode) => { children.push(createVirtualDom(cnode, isSVG)); }); return children.filter(c => !!c);}function getNodeAttributes(element, isSVG = false) { const attributes = element.attributes ? [...element.attributes] : []; const attr = {}; let namespace; attributes.forEach(({ nodeName, nodeValue }) => { attr[nodeName] = nodeValue; if (XML_NAMESPACES.includes(nodeName)) { namespace = nodeValue; } else if (isSVG) { namespace = SVG_NAMESPACE; } }); return { attr, namespace };}通过以上代码,我们可以将整个documentElement转化为Virtual DOM,其中__flow用来记录一些参数,包括标记ID等,Virtual Node记录了:type、attributes、children、namespace。 ...

September 9, 2019 · 4 min · jiezi

JavaScript深入浅出第5课Chrome是如何成功的

摘要: Chrome改变世界。 《JavaScript深入浅出》系列: JavaScript深入浅出第1课:箭头函数中的this究竟是什么鬼?JavaScript深入浅出第2课:函数是一等公民是什么意思呢?JavaScript深入浅出第3课:什么是垃圾回收算法?JavaScript深入浅出第4课:V8引擎是如何工作的?JavaScript深入浅出第5课:Chrome是如何成功的?前言在上一篇博客中,我聊了一下JavaScript引擎V8的工作原理,顺其自然,接下来应该来聊聊渲染引擎Blink或者Chrome浏览器的工作原理。但是,这2个坑以后再填。 这次我重点聊聊产品,当然免不了涉及一些技术。 几乎所有JavaScript开发者每天都在使用Chrome,大家知道它是如何成为浏览器霸主的吗? Google为什么要做浏览器?其实,Google的联合创始人Larry Page和Sergey Brin早在2001年就想做浏览器,但是当时的CEO施密特一直反对,因为从头开发一个浏览器的成本太高了,不是一个创业公司可以承受的。因此,Google直到2006年,公司已经上市2年了,才开始做浏览器,秘密开发了2年,Chrome才正式发布。 Google真正开始开发Chrome是2006年,当时IE的市场占有率高达80%,Firefox大概是10%。自从击败Netscape之后,IE似乎可以高枕无忧了。如果那时候有人要做一个浏览器,大多数人都会质疑,还需要多个浏览器干嘛?IE和Firefox又不是不能用。 但是,2006年时的Web早已经不再是简单的静态页面,Gmail、Youtube、Google Maps,Facebook这些复杂的Web应用已经出现一段时间了,传统浏览器在架构、性能以及稳定性上已经逐渐不再适用了,这时正是需要一款更加强大的浏览器来满足用户与Web开发者的需求。 Google所做的最重要的事情,就是对成千上万的网页进行排序,所以它存在的意义是基于网页的。而一个更快、更好的浏览器,可以促进Web技术的发展,网页会越来越多,越来越好,用户花在Web上的时间越来越多,这对Google是有益。因此,Google要做浏览器,不只是想要一个搜索入口那么简单。 Google希望通过Chrome浏览器来促进Web技术的发展,从而让自己受益,这也不是什么秘密,Chrome团队的人都是这么说的,Google现在的CEO是Sundar Pichai,他当年发布Chrome的时候是这样说的: We hope to collaborate with the entire community to help drive the web forward.这样假大空的话当年大概没几个人相信,但是这不重要,重要的是Google真的做到了,Chrome确实推动了Web技术的发展。没有Chrome的话,现在的Web技术大概确实得落后不少。 如果Google只是想要一个搜索入口,它可以收购一个浏览器,或者基于开源浏览器套一个壳,做一下账户系统就够了,再通过Google网站进行推广。国内各个大厂的浏览器都是基于Chrome的开源版本Chromium实现的,某个浏览器甚至直接打包了Chrome的安装包。 既然Google想做的事情是推动Web技术发展,如果沿用旧的思想和技术的话,显然是做不到的。于是,他们设计了一个多进程的浏览器架构,重新写了一个性能彪悍的JavaScript引擎V8,后来又基于Webkit做了一个新的渲染引擎Blink。 不妨这样说,Google与国内的搜索引擎巨头们的还差一个Chrome浏览器。后者看到的是搜索流量带来的商业价值以及重新开发一个浏览器的巨大成本,而前者看到了Web技术发展对搜索引擎本身的长远价值。 Chrome就一定能成功吗?Google终于决定做浏览器了,但这事能不能做成,其实也不一定。和每一个大公司一样,Google失败的项目远远多于成功的项目,大家不妨看看Killed by Google里面的列表。 Google确实有很多非常成功的产品,比如Android,Youtube,Google Maps, DeepMind,但是它们其实都是收购来的。Chrome算是Google为数不多的真正从零开始打造出来的产品。 下面这张图是Chrome发布时的照片: 图片来源:Niall Kennedy 照片中从左至右是Larry Page, Brian Rakowski, Sundar Pichai, Sergey Brin, Darin Fisher, Lars Bak和Ben Goodger,他们都是Chrome浏览器最关键人物,也都因为Chrome的成功而收益不菲。 Larry Page和Sergey Brin是Google的创始人,他们一直希望做浏览器;Sundar Pichai当时是Google负责产品的副总裁,Chrome也在他的管理范围之类,现在他是Google的CEO;Brian Rakowski当时是Chrome的产品经理,现在是Google负责产品的副总裁;Lars Bak是JavaScript引擎V8的负责人,曾长期从事编程语言的虚拟机开发工作;Darin Fisher是Chrome最早期的开发者,之前是Firefox的工程师,现在是Google负责Chrome的副总裁;Ben Goodger是Chrome最早期的开发者,之前是Firefox的工程师,现在的职级为Distinguished Engineer,仅次于Google Fellow以及Senior Google Fellow;照片中大家都挺开心的,秘密开发了2年的Chrome终于发布了,但是他们能想到10年后Chrome可以占有接近70%的市场份额吗? ...

August 8, 2019 · 2 min · jiezi

JavaScript深入浅出第4课V8引擎是如何工作的

摘要: 性能彪悍的V8引擎。 《JavaScript深入浅出》系列: JavaScript深入浅出第1课:箭头函数中的this究竟是什么鬼?JavaScript深入浅出第2课:函数是一等公民是什么意思呢?JavaScript深入浅出第3课:什么是垃圾回收算法?JavaScript深入浅出第4课:V8是如何工作的?最近,JavaScript生态系统又多了2个非常硬核的项目。 大神Fabrice Bellard发布了一个新的JS引擎QuickJS,可以将JavaScript源码转换为C语言代码,然后再使用系统编译器(gcc或者clang)生成可执行文件。 Facebook为React Native开发了新的JS引擎Hermes,用于优化安卓端的性能。它可以在构建APP的时候将JavaScript源码编译为Bytecode,从而减少APK大小、减少内存使用,提高APP启动速度。 作为JavaScript程序员,只有极少数人有机会和能力去实现一个JS引擎,但是理解JS引擎还是很有必要的。本文将介绍一下V8引擎的原理,希望可以给大家一些帮助。 JavaScript引擎我们写的JavaScript代码直接交给浏览器或者Node执行时,底层的CPU是不认识的,也没法执行。CPU只认识自己的指令集,指令集对应的是汇编代码。写汇编代码是一件很痛苦的事情,比如,我们要计算N阶乘的话,只需要7行的递归函数: function factorial(N) { if (N === 1) { return 1; } else { return N * factorial(N - 1); }}代码逻辑也非常清晰,与阶乘数的学定义完美吻合,哪怕不会写代码的人也能看懂。 但是,如果使用汇编语言来写N阶乘的话,要300+行代码n-factorial.s: 这个N阶乘的汇编代码是我大学时期写的,已经是N年前的事情了,它需要处理10进制与2进制的转换,需要使用多个字节保存大整数,最多可以计算大概500左右的N阶乘。 还有一点,不同类型的CPU的指令集是不一样的,那就意味着得给每一种CPU重写汇编代码,这就很崩溃了。。。 还好,JavaScirpt引擎可以将JS代码编译为不同CPU(Intel, ARM以及MIPS等)对应的汇编代码,这样我们才不要去翻阅每个CPU的指令集手册。当然,JavaScript引擎的工作也不只是编译代码,它还要负责执行代码、分配内存以及垃圾回收。 虽然浏览器非常多,但是主流的JavaScirpt引擎其实很少,毕竟开发一个JavaScript引擎是一件非常复杂的事情。比较出名的JS引擎有这些: V8 (Google)SpiderMonkey (Mozilla)JavaScriptCore (Apple)Chakra (Microsoft)IOT:duktape、JerryScript还有,最近发布QuickJS与Hermes也是JS引擎,它们都超越了浏览器范畴,Atwood's Law再次得到了证明: Any application that can be written in JavaScript, will eventually be written in JavaScript.V8:强大的JavaScript引擎在为数不多JavaScript引擎中,V8无疑是最流行的,Chrome与Node.js都使用了V8引擎,Chrome的市场占有率高达60%,而Node.js是JS后端编程的事实标准。国内的众多浏览器,其实都是基于Chromium浏览器开发,而Chromium相当于开源版本的Chrome,自然也是基于V8引擎的。神奇的是,就连浏览器界的独树一帜的Microsoft也投靠了Chromium阵营。另外,Electron是基于Node.js与Chromium开发桌面应用,也是基于V8的。 V8引擎是2008年发布的,它的命名灵感来自超级性能车的V8引擎,敢于这样命名确实需要一些实力,它性能确实一直在稳步提高,下面是使用Speedometer benchmark的测试结果: V8在工业界已经非常成功了,同时它还获得了学术界的肯定,拿到了ACM SIGPLAN的Programming Languages Software Award: V8's success is in large part due to the efficient machine code it generates.Because JavaScript is a highly dynamic object-oriented language, many experts believed that this level of performance could not be achieved. V8's performance breakthrough has had a major impact on the adoption of JavaScript, which is nowadays used on the browser, the server, and probably tomorrow on the small devices of the internet-of-things.JavaScript是一门动态类型语言,这会给编译器增加很大难度,因此专家们觉得它的性能很难提高,但是V8居然做到了,生成了非常高效的machine code(其实是汇编代码),这使得JS可以应用在各个领域,比如Web、APP、桌面端、服务端以及IOT。 ...

July 16, 2019 · 3 min · jiezi

一步一步搭建前端监控系统接口请求异常监控篇

摘要: 如何监控HTTP请求错误? 作者:一步一个脚印一个坑原文:搭建前端监控系统(四)接口请求异常监控篇Fundebug经授权转载,版权归原作者所有。 背景:市面上的监控系统有很多,大多收费,对于小型前端项目来说,必然是痛点。另一点主要原因是,功能虽然通用,却未必能够满足我们自己的需求, 所以我们自给自足也许是个不错的办法。 这是搭建前端监控系统的第四章,主要是介绍如何统计静态资源加载报错,跟着我一步步做,你也能搭建出一个属于自己的前端监控系统。 上一章介绍了如何统计静态资源加载报错,今天要说的是接口请求报错。可能有人会认为接口的报错应该由后台来关注,统计,并修复。 确实如此,而且后台服务有了很多成熟完善的统计工具,完全能够应对大部分的异常情况, 那么为什么还需要前端对接口请求进行监控呢。原因很简单,因为前端是bug的第一发现位置,在你帮后台背锅之前怎么快速把过甩出去呢,这时候,我们就需要有一个接口的监控系统,哈哈 :) 我们要监控接口报错的情况,及时定位线上问题产生的原因我们要分析接口的性能,以辅助我们对前端应用的优化。好了, 进入正题吧: 如何监控前端接口请求呢? 一般前端请求都是用jquery的ajax请求,也有用fetch请求的,以及前端框架自己封装的请求等等。总之他们封装的方法各不相同,但是万变不离其宗,他们都是对浏览器的这个对象 window.XMLHttpRequest 进行了封装,所以我们只要能够监听到这个对象的一些事件,就能够把请求的信息分离出来。 1. 如何监听ajax请求如果你用的jquery、zepto、或者自己封装的ajax方法,就可以用如下的方法进行监听。我们监听 XMLHttpRequest 对象的两个事件 loadstart, loadend。但是监听的结果并不是像我们想象的那么容易理解,我们先看下ajaxLoadStart,ajaxLoadEnd的回调方法。 /** * 页面接口请求监控 */function recordHttpLog() { // 监听ajax的状态 function ajaxEventTrigger(event) { var ajaxEvent = new CustomEvent(event, { detail: this }); window.dispatchEvent(ajaxEvent); } var oldXHR = window.XMLHttpRequest; function newXHR() { var realXHR = new oldXHR(); realXHR.addEventListener( "loadstart", function() { ajaxEventTrigger.call(this, "ajaxLoadStart"); }, false ); realXHR.addEventListener( "loadend", function() { ajaxEventTrigger.call(this, "ajaxLoadEnd"); }, false ); // 此处的捕获的异常会连日志接口也一起捕获,如果日志上报接口异常了,就会导致死循环了。 // realXHR.onerror = function () { // siftAndMakeUpMessage("Uncaught FetchError: Failed to ajax", WEB_LOCATION, 0, 0, {}); // } return realXHR; } var timeRecordArray = []; window.XMLHttpRequest = newXHR; window.addEventListener("ajaxLoadStart", function(e) { var tempObj = { timeStamp: new Date().getTime(), event: e }; timeRecordArray.push(tempObj); }); window.addEventListener("ajaxLoadEnd", function() { for (var i = 0; i < timeRecordArray.length; i++) { if (timeRecordArray[i].event.detail.status > 0) { var currentTime = new Date().getTime(); var url = timeRecordArray[i].event.detail.responseURL; var status = timeRecordArray[i].event.detail.status; var statusText = timeRecordArray[i].event.detail.statusText; var loadTime = currentTime - timeRecordArray[i].timeStamp; if (!url || url.indexOf(HTTP_UPLOAD_LOG_API) != -1) return; var httpLogInfoStart = new HttpLogInfo( HTTP_LOG, url, status, statusText, "发起请求", timeRecordArray[i].timeStamp, 0 ); httpLogInfoStart.handleLogInfo(HTTP_LOG, httpLogInfoStart); var httpLogInfoEnd = new HttpLogInfo( HTTP_LOG, url, status, statusText, "请求返回", currentTime, loadTime ); httpLogInfoEnd.handleLogInfo(HTTP_LOG, httpLogInfoEnd); // 当前请求成功后就在数组中移除掉 timeRecordArray.splice(i, 1); } } });}一个页面上会有很多个请求,当一个页面发出多个请求的时候,ajaxLoadStart事件被监听到,但是却无法区分出来到底发送的是哪个请求,只返回了一个内容超多的事件对象,而且事件对象的内容几乎完全一样。当ajaxLoadEnd事件被监听到的时候,也会返回一个内容超多的时间对象,这个时候事件对象里包含了接口请求的所有信息。幸运的是,两个对象是同一个引用,也就意味着,ajaxLoadStart和ajaxLoadEnd事件被捕获的时候,他们作用的是用一个对象。那我们就有办法分析出来了。 ...

July 13, 2019 · 2 min · jiezi

99的程序都没有考虑的网络异常使用Fundebugnotify主动上报

近日看到一篇文章99%的程序都没有考虑的网络异常,开篇提到: 绝大多数程序只考虑了接口正常工作的场景,而用户在使用我们的产品时遇到的各类异常,全都丢在看似 ok 的 try catch 中。如果没有做好异常的兼容和兜底处理,会极大的影响用户体验,严重的还会带来安全和资损风险。于是,笔者分析了 GitHub 上的一些开源微信小程序,发现大多数的代码异常处理确实是不够的。 登录接口只考虑成功的情况,没考虑失败的情况//调用登录接口wx.login({ success: function() { wx.getUserInfo({ success: function(res) { that.globalData.userInfo = res.userInfo; typeof cb == "function" && cb(that.globalData.userInfo); } }); }});网络请求只考虑then不考虑catch util.getData(index_api).then(function(data) { //this.setData({ // //}); console.log(data);});考虑了异常情况但是没有做妥善的处理 db.collection("config") .where({}) .get() .then(res => { console.log(res); if (res.data.length > 0) { Taro.setStorage({ key: "config_gitter", data: res.data[0] }); } }) .catch(err => { console.error(err); });也许 99%的情况下接口都是正常返回的,只有 1%的情况会失败。看起来好像不是一件严重的事情,但是考虑到用户的量级,这个事情就不那么简单了。假设有 100 万用户,那么就有 1 万用户遇到异常情况,而且如果用户的使用频次很高,影响的何止 1 万用户。并且,如今产品都是体验至上,如果遇到这样的问题,用户极大可能就弃你而去,流失了客户就等于流失了收入。 ...

July 8, 2019 · 1 min · jiezi

JavaScript深入浅出第1课箭头函数中的this究竟是什么鬼

摘要: 箭头函数极大地简化了this的取值规则。 普通函数与箭头函数普通函数指的是用function定义的函数: var hello = function () { console.log("Hello, Fundebug!");}箭头函数指的是用=>定义的函数: var hello = () => { console.log("Hello, Fundebug!");}JavaScript箭头函数与普通函数不只是写法上的区别,它们还有一些微妙的不同点,其中一个不同点就是this。 箭头函数没有自己的this值,箭头函数中所使用的this来自于函数作用域链。这句话很简单,不过听着稍微有点莫名其妙,得从头说起。 this到底是什么?关于this的文章也够多了,有时候越描越黑,我就不再添乱了,我只负责搬运一下MDN文档:this,感兴趣的可以仔细阅读一下,我摘录一些最重要的话就好了。 A function's this keyword behaves a little differently in JavaScript compared to other languages. It also has some differences between strict mode and non-strict mode.JavaScript是一门比较奇特的语言,它的this与其他语言不一样,并且它的取值还取决于代码是否为严格模式("use strict")。 this的值是什么? The JavaScript context object in which the current code is executing.this就是代码执行时当前的context object。 Global context In the global execution context (outside of any function), this refers to the global object whether in strict mode or not.代码没有在任何函数中执行,而是在全局作用域中执行时,this的值就是global对象,对于浏览器来说,this就是window。 ...

June 18, 2019 · 2 min · jiezi

Nodejs官方文档到底什么是阻塞Blocking与非阻塞NonBlocking

译者按: Node.js文档阅读系列之一。 原文: Overview of Blocking vs Non-Blocking译者: Fundebug为了保证可读性,本文采用意译而非直译。 这篇博客将介绍Node.js的阻塞(Blocking)与非阻塞(Non-Blocking)。我会提到Event Loop与libuv,但是不了解它们也不会影响阅读。读者只需要有一定的JavaScript基础,理解Node.js的回调函数(callback pattern)就可以了。 博客中提到了很多次I/O,它主要指的是使用libuv与系统的磁盘与网络进行交互。 阻塞(Blocking)阻塞指的是一部分Node.js代码需要等到一些非Node.js代码执行完成之后才能继续执行。这是因为当阻塞发生时,Event Loop无法继续执行。 对于Node.js来说,由于CPU密集的操作导致代码性能很差时,不能称为阻塞。当需要等待非Node.js代码执行时,才能称为阻塞。Node.js中依赖于libuv的同步方法(以Sync结尾)导致阻塞,是最常见的情况。当然,一些不依赖于libuv的原生Node.js方法有些也能导致阻塞。 Node.js中所有与I/O相关的方法都提供了异步版本,它们是非阻塞的,可以指定回调函数,例如fs.readFile。其中一些方法也有对应的阻塞版本,它们的函数名以Sync结尾,例如fs.readFileSync。 代码示例阻塞的方法是同步执行的,而非阻塞的方法是异步执行。 以读文件为例,下面是同步执行的代码: const fs = require('fs');const data = fs.readFileSync('/file.md'); // 文件读取完成之前,代码会阻塞,不会执行后面的代码console.log("Hello, Fundebug!"); // 文件读取完成之后才会打印对应的异步代码如下: const fs = require('fs');fs.readFile('/file.md', (err, data) => { if (err) throw err;}); // 代码不会因为读文件阻塞,会继续执行后面的代码console.log("Hello, Fundebug!"); // 文件读完之前就会打印第一个示例代码看起来要简单很多,但是它的缺点是会阻塞代码执行,后面的代码需要等到整个文件读取完成之后才能继续执行。 在同步代码中,如果读取文件出错了,则错误需要使用try...catch处理,否则进程会崩溃。对于异步代码,是否处理回调函数的错误则取决于开发者。 我们可以将示例代码稍微修改一下,下面是同步代码: const fs = require('fs');const data = fs.readFileSync('/file.md'); console.log(data);moreWork(); // console.log之后再执行异步代码如下: const fs = require('fs');fs.readFile('/file.md', (err, data) => { if (err) throw err; console.log(data);});moreWork(); // 先于console.log执行在第一个示例中,console.log将会先于moreWork()执行。在第二个示例中,由于fs.readFile()是非阻塞的,代码可以继续执行,因此moreWork()会先于console.log执行。 ...

June 12, 2019 · 1 min · jiezi

我是怎样用函数式JavaScript计算数组平均值的

译者按: 有时候一个算法的直观、简洁、高效是需要作出取舍的。 原文: FUNCTIONAL JAVASCRIPT: FIVE WAYS TO CALCULATE AN AVERAGE WITH ARRAY REDUCE译者: Fundebug本文采用意译,版权归原作者所有 函数式编程中用于操作数组的方法就像“毒品”一样,它让很多人爱上函数式编程。因为它们真的十分常用而且又超级简单。 .map() 和 .filter()都仅需一个参数,该参数定义操作数组每一个元素的函数即可。reduce()会复杂一些,我之前写过一篇文章介绍为什么人们难以掌握reduce()方法,其中一个原因在于很多入门资料都仅仅用算术作为例子。我写了很多用reduce()来做算术以外的例子。 用reduce()来计算数组的平均值是一个常用的模式。代码看起来非常简单,不过在计算最终结果之前你需要做两个准备工作: 数组的长度数组所有元素之和这两个事情看起来都很简单,那么计算数组的平均值并不是很难了吧。解法如下: function average(nums) { return nums.reduce((a, b) => a + b) / nums.length;}确实不是很难,是吧?但是如果数据结构变得复杂了,就没那么简单了。比如,数组里面的元素是对象,你需要先过滤掉某些对象,然后从对象中取出数字。这样的场景让计算平均值变得复杂了一点。 接下来我们处理一个类似的问题(从this Free Code Camp challenge获得灵感),我们会提供 5 种不同的解法,每一种方法有各自的优点和缺点。这 5 种方法也展示了 JavaScript 的灵活。我希望可以给你在使用reduce的实战中一些灵感。 问题提出假设我们有一个数组,记录了维多利亚时代常用的口语。接下来我们要找出那些依然现存于 Google Books 中的词汇,并计算他们的平均流行度。数据的格式是这样的: const victorianSlang = [ { term: "doing the bear", found: true, popularity: 108 }, { term: "katterzem", found: false, popularity: null }, { term: "bone shaker", found: true, popularity: 609 }, { term: "smothering a parrot", found: false, popularity: null }, { term: "damfino", found: true, popularity: 232 }, { term: "rain napper", found: false, popularity: null }, { term: "donkey’s breakfast", found: true, popularity: 787 }, { term: "rational costume", found: true, popularity: 513 }, { term: "mind the grease", found: true, popularity: 154 }];接下来我们用 5 中不同的方法计算平均流行度值。 ...

June 5, 2019 · 3 min · jiezi

线上出bug了别怕这么定位

摘要: Source Map还是很神奇的。 原文:线上出bug了?别怕,这么定位!公众号:前端小苑Fundebug经授权转载并修改,版权归原作者所有。 工作中,生产环境代码是编译后代码,搜集到报错信息的行和列无法在源码中对应,很多时候只能靠“经验”去猜,本文针对这种情况,开发了一个npm命令行小工具,帮助快速定位报错的源码位置,提升效率。 由于现在构建工具盛行,前端部署的代码都是经过编译,压缩后的,于是乎,SoueceMap就扮演了一个十分重要的角色,用来作为源代码和编译代码之间的映射,方便定位问题。 测试SourceMap功能首先全局安装reverse-sourcemap npm install --global reverse-sourcemap选择编译后的代码进行测试,下面是vue项目编译生成的代码。 在命令行执行命令,将main.js反编译回源码,并输出到sourcecode目录下。 reverse-sourcemap -v dist/main.a8ebc11c3f03786d8e3b.js.map -o sourcecode 上面是执行命令输出的sourcecode目录,生成的目录结构和源码目录一致,打开一个文件,和源码做下对比: 可以看出,反编译出的代码无论目录结构还是具体代码都和源码一致。 生产环境代码报错如何定位源码位置如果使用了Fundebug的Source Map功能的话,则可以很方便的定位出错位置: 如果没有使用监控工具的话,生产环境的代码,经过压缩、编译,很不利于Debug。针对这个问题,需要准备一份生产环境代码的map文件,为了方便,可以在项目的package.json增加debug命令用来生成map文件。这条命令除了开启sourcemap,其他的具体webpack配置和生产环境配置相同。 "scripts": { "start": "vue-cli-service serve --mode dev", "stage": "vue-cli-service build --mode staging", "online": "vue-cli-service build", "debug": "vue-cli-service build --mode debug" }有了map文件,通过SourceMap提供的API就可以定位到源码的位置。下面是实现的核心代码。 // Get file contentconst sourceMap = require('source-map');const readFile = function (filePath) { return new Promise(function (resolve, reject) { fs.readFile(filePath, {encoding:'utf-8'}, function(error, data) { if (error) { console.log(error) return reject(error); } resolve(JSON.parse(data)); }); });};// Find the source locationasync function searchSource(filePath, line, column) { const rawSourceMap = await readFile(filePath) const consumer = await new sourceMap.SourceMapConsumer(rawSourceMap); const res = consumer.originalPositionFor({ 'line' : line, 'column' : column }); consumer.destroy() return res}最重要的就是使用SourceMap提供的 originalPositionFor API。 SourceMapConsumer.prototype.originalPositionFor(generatedPosition) ...

June 1, 2019 · 1 min · jiezi