JS 考题

1. 分析输出 for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 1) } for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 1) } 2. 分析输出 const shape = { radius: 10, diameter() { return this.radius * 2 }, perimeter: () => 2 * Math.PI * this.radius, } shape.diameter() shape.perimeter() 3. 分析输出 const a = {} function test1(a) { a = { name: 'wdd', } } function test2() { test1(a) } function test3() { console....

2022-11-04 08:42:15 · 2 min · Eddie Wang

JavaScript内存泄露分析

参考: http://slides.com/gruizdevilla/memory 内存是一张图 原始类型,只能作为叶子。原始类型不能引用其他类型 数字 字符串 布尔值 除了原始类型之外,其他类型都是对象,其实就是键值对 数组是一种特殊对象,它的键是连续的数字 内存从根开始 在浏览器中,根对象是window 在nodejs中,根对象是global 任何从根无法到达的对象,都会被GC回收,例如下图的节点9和10 根节点的GC是无法控制的 路径 从根节点开始到特定对象的路径,如下图的1-2-4-6-8 支配项 每个对象有且仅有一个支配项,支配项对对象可能不是直接引用 举例子 节点1支配节点2 节点2支持节点3、4、6 节点3支配节点5 节点6支配节点7 节点5支配节点8 上面的例子有个不好理解的是节点2为什么支配了节点6?如果节点A存在于从根节点到节点B的每一个路径中,那么A就是B的支配项。2存在于1-2-4-6,也存在于1-2-3-6,所以节点2支配节点6 V8 新生代与老生代 v8内存分为新生代和老生代内存,两块内存使用不同的内存GC策略 相比而言,新生代GC很快,老生代则较慢 新生代的内存在某些条件下会被转到老生代内存区 GC发生时,用可能应用会暂停 解除引用的一些错误 var a = {name: 'wdd'} delete a.name // 这回让对象a变成慢对象 var a = {name: 'wdd'} a.name = null // 这个则更好 关于slow Object V8 optimizing compiler makes assumptions on your code to make optimizations. It transparently creates hidden classes that represent your objects....

2022-10-29 11:52:59 · 2 min · Eddie Wang

JS内存泄漏分享

什么是内存泄漏? 单位时间内的内存变化量可能有三个值 正数:内存可能存在泄漏。生产环境,如果服务在启动后,该值一直是正值,从未出现负值或者趋近于0的值,那么极大的可能是存在内存泄漏的。 趋近于0的值: 内存稳定维持 负数:内存在释放 实际上,在观察内存变化量时,需要有两个前提条件 一定的负载压力:因为在开发或者功能测试环境,很少的用户,服务的压力很小,是很难观测到内存泄漏问题的。所以务必在一定的负载压力下观测。 至少要观测一天:内存上涨并不一定意味着存在内存泄漏问题。在一个工作日中,某些时间点,是用户使用的高峰期,服务的负载很高,自然内存使用会增长。关键在于在高峰期过后的低谷期时,内存是否回下降到正常值。如果内存在低谷期时依然维持着高峰期时的内存使用,那么非常大可能是存在内存泄漏了。 下图是两个服务的。从第一天的0点开始观测服务的内存,一直到第二天的12点。正常的服务会随着负载的压力增加或者减少内存使用。而存在内存泄漏的服务,内存一直在上升,并且负载压力越大,上升的越快。 有没有可能避免内存泄漏? 除非你不写代码,否者你是无法避免内存泄漏的问题的。 第一,即使你是非常精通某个语言,也是有很多关于如何避免内存泄漏的经验。但是你的代码里仍然可能会包含其他库或者其他同事写的代码,那些代码里是无法保证是否存在内存泄漏问题的。 第二,内存泄漏的代码有时候非常难以察觉。例如console.log打印的太快,占用太多的buffer。网络流量激增,占用太多的Recv_Q,node无法及时处理。写文件太慢,没有处理“后压”相关的逻辑等等。 为什么要关注内存泄漏? 为什么要关注内存泄漏?我们客户的服务器可是有500G内存的 你可能有个很豪的金主。但是你不要忘记一个故事。 传说国际象棋是由一位印度数学家发明的。国王十分感谢这位数学家,于是就请他自己说出想要得到什么奖赏。这位数学家想了一分钟后就提出请求——把1粒米放在棋盘的第1格里,2粒米放在第2格,4粒米放在第3格,8粒米放在第4格,依次类推,每个方格中的米粒数量都是之前方格中的米粒数量的2倍。 国王欣然应允,诧异于数学家竟然只想要这么一点的赏赐——但随后却大吃了一惊。当他开始叫人把米放在棋盘上时,最初几个方格中的米粒少得像几乎不存在一样。但是,往第16个方格上放米粒时,就需要拿出1公斤的大米。而到了第20格时,他的那些仆人则需要推来满满一手推车的米。国王根本无法提供足够的大米放在棋盘上的第64格上去。因为此时,棋盘上米粒的数量会达到惊人的18 446 744 073 709 551 615粒。如果我们在伦敦市中心再现这一游戏,那么第64格中的米堆将延伸至M25环城公路,其高度将超过所有建筑的高度。事实上,这一堆米粒比过去1000年来全球大米的生产总量还要多得多。 对于内存泄漏来说,可能500G都是不够用的。 实际上操作系统对进程使用内存资源是有限制的,我们关注内存泄漏,实际上是关注内存泄漏会引起的最终问题:out of memory。如果进程使用的资源数引起了操作系统的注意,很可能进程被操作系统杀死。 然后你的客户可能正在使用你的服务完成一个重要的事情,接着你们的客户投诉热线回被打爆,然后是你的老板,你的领导找你谈话~~~ 基本类型 vs 引用类型 基本类型:undefined, null, boolean, number, string。基本类型是按值访问 引用类型的值实际上是指向内存中的对象 上面的说法来自《JavaScript高级程序设计》。但是对于基本类型字符串的定义,实际上我是有些不认同的。有些人也认为字符串不属于基本类型。 就是关于字符串,我曾思考过,在JavaScript里,字符串的最大长度是多少,字符串最多能装下多少个字符? 我个人认为,一个变量有固定的大小的内存占用,才是基本类型。例如数字,null, 布尔值,这些值很容易能理解他们会占用固定的内存大小。但是字符串就不一样了。字符串的长度是不固定,在不同的浏览器中,有些字符串最大可能占用256M的内存,甚至更多。 可以参考这个问题:https://stackoverflow.com/questions/34957890/javascript-string-size-limit-256-mb-for-me-is-it-the-same-for-all-browsers 内存是一张图 1代表根节点,在NodeJS里是global对象,在浏览器中是window对象 2-6代表对象 7-8代表原始类型。分别有三种,字符串,数字,布尔值 9-10代表从根节点无法到达的对象 注意,作为原始类型的值,在内存图中只能是叶子节点。 ** 从跟节点R0无法到达的节点9,10,将会在GC时被清除。 保留路径的含义是从跟对象到某一节点的最短路径。例如1->2->4->6。 对象保留树 节点: 构造函数的名称 边缘:对象的key 距离: 节点到跟节点的最短距离 支配项(Dominators) 每个对象有且仅有一个支配项 如果B存在从根节点到A节点之间的所有路径中,那么B是A的支配项,即B支配A。 下图中 1支配2 2支配3,4,6 (想想2为什么没有支配5?) 3支配5 6支配7 5支配8 理解支配项的意义在于理解如何将资源释放。如下图所示,如果目标是释放节点6的占用资源,仅仅释放节点3或者节点4是没有用的,必需释放其支配项节点2,才能将节点6释放。 对象大小 对象自身占用大小:shadow size 通过保持对其他对象的引用隐式占用,这种方式可以阻止这些对象被垃圾回收器(简称 GC)自动处置 对象的大小的单位是字节 分析工具 heapsnapshot import {writeHeapSnapshot} from 'v8' router....

2022-10-29 11:47:38 · 2 min · Eddie Wang

NodeJS Out of Memory: Backpressuring in Streams

今天我收集了一份大概有40万行的日志,为了充分利用这份日志,我决定把日志给解析,解析完了之后,再写入mysql数据库。 首先,对于40万行的日志,肯定不能一次性读取到内存。 所以我用了NodeJs内置的readline模块。 const readline = require('readline') let line_no = 0 let rl = readline.createInterface({ input: fs.createReadStream('./my.log') }) rl.on('line', function(line) { line_no++; console.log(line) }) // end rl.on('close', function(line) { console.log('Total lines : ' + line_no); }) 数据解析以及写入到这块我没有贴代码。代码的执行是正常的,但是一段时间之后,程序就报错Out Of Memory。 代码执行是在nodejs 10.16.3上运行的,谷歌搜了一下解决方案,看到有人说nodejs升级到12.x版本就可以解决这个问题。我抱着试试看的想法,升级了nodejs到最新版,果然没有再出现OOM的问题。 后来我想,我终于深刻理解了NodeJS官网上的这篇文章 Backpressuring in Streams,以前我也度过几遍,但是不太了解,这次接合实际情况。有了深刻理解。 NodeJS在按行读取本地文件时,大概可以达到每秒1000行的速度,然而数据写入到MySql,大概每秒100次插入的样子。 本身网络上存在的延迟就要比读取本地磁盘要慢,读到太多的数据无法处理,只能暂时积压到内存中,然而内存有限,最终OOM的异常就抛出了。 NodeJS 12.x应该解决了这个问题。 参考 https://nodejs.org/en/docs/guides/backpressuring-in-streams/

2022-10-29 11:46:07 · 1 min · Eddie Wang

new Date('time string')的陷阱

一般情况下,建议你不要用new Date(“time string”)的方式去做时间解析。因为不同浏览器,可能接受的time string的格式都不一样。 你最好不要去先入为主,认为浏览器会支持的你的格式。 常见的格式 2010-10-10 19:00:00 就这种格式,在IE11上是不接受的。 下面的比较,在IE11上返回false, 在chrome上返回true。原因就在于,IE11不支持这种格式。 new Date() > new Date('2010-10-10 19:00:00') 所以在时间处理上,最好选用比价靠谱的第三方库,例如dayjs, moment等等。 千万不要先入为主!!

2022-10-29 11:44:26 · 1 min · Eddie Wang

IE8/9 支持WebSocket方案,flash安全策略

IE8/9原生是不支持WebSocket的,但是我们可以使用flash去模拟一个WebSocket接口出来。 这方面,https://github.com/gimite/web-socket-js 已经可以使用。 除了客户端之外,服务端需要做个flash安全策略设置。 这里的服务端是指WebSocet服务器所在的服务端。默认端口是843端口。 客户端使用flash模拟WebSocket时,会打开一个到服务端843端口的TCP链接。 并且发送数据: <policy-file-request>. 服务端需要回应下面类似的内容 <?xml version="1.0"?> <!DOCTYPE cross-domain-policy SYSTEM "/xml/dtds/cross-domain-policy.dtd"> <cross-domain-policy> <site-control permitted-cross-domain-policies="all"/> <allow-access-from domain="*" to-ports="*" secure="false"/> <allow-http-request-headers-from domain="*" headers="*"/> </cross-domain-policy> Node.js实现 policy.js module.exports.policyFile = `<?xml version="1.0"?> <!DOCTYPE cross-domain-policy SYSTEM "/xml/dtds/cross-domain-policy.dtd"> <cross-domain-policy> <site-control permitted-cross-domain-policies="all"/> <allow-access-from domain="*" to-ports="*" secure="false"/> <allow-http-request-headers-from domain="*" headers="*"/> </cross-domain-policy>` index.js const Net = require('net') const {policyFile} = require('./policy') const port = 843 console.log(policyFile) const server = new Net.Server() server.listen(port, function() { console.log(`Server listening for connection requests on socket localhost:${port}`); }); server....

2022-10-29 11:43:23 · 1 min · Eddie Wang

为什么vscode会占用大量CPU资源?

电脑的风扇声突然响了起来,我知道有某个进程在占用大量CPU资源。 在任务管理器中,可以看到vscode占用的的CPU资源达到150。说明问题出在vscode上。 在vscode中,按F1, 输入: show running extensions 可以查看所有插件的运行状况。 其中需要关注最重要的指标就是活动时间:如果某个插件的活动时间明显是其他插件的好多倍,那问题就可能出在这个插件上。要么禁用该插件,要么卸载该插件。

2022-10-29 11:42:19 · 1 min · Eddie Wang

js中二进制的操作

js原生支持16进制、10进制、8进制的直接定义 var a = 21 // 十进制 var b = 0xee // 十六进制, 238 var c = 013 // 八进制 11 十进制转二进制字符串 var a = 21 // 十进制 a.toString(2) // "10101" 二进制转10进制 var d = "10101" parseInt('10101',2) // 21

2022-10-29 11:41:13 · 1 min · Eddie Wang

NodeJS边下载边解压gz文件

const fs = require('fs') var request = require('request') const zlib = require('zlib') const log = require('./log.js') const fileType = '' let endCount = 0 module.exports = (item) => { return new Promise((resolve, reject) => { request.get(item.url) .on('error', (error) => { log.error(`下载失败${item.name}`) reject(error) }) .pipe(zlib.createGunzip()) .pipe(fs.createWriteStream(item.name + fileType)) .on('finish', (res) => { log.info(`${++endCount} 完成下载 ${item.name + fileType}`) resolve(res) }) }) }

2022-10-29 11:39:37 · 1 min · Eddie Wang

V8 Profile

下面的命令可以生成一个v8的日志如 isolate-0x102d4e000-86008-v8.log –log-source-code 不是必传的字段,加了该字段可以在定位到源码 node --prof --log-source-code index.js 下一步是将log文件转成json node --prof-process --preprocess isolate-0x102d4e000-86008-v8.log > v8.json 然后打开 https://wangduanduan.gitee.io/v8-profiling/ 这个页面,选择v8.json 下图横坐标是时间,纵坐标是cpu百分比。 选择Bottom Up之后,展开JS unoptimized, 可以发现占用cpu比较高的代码的位置。

2022-10-29 11:37:55 · 1 min · Eddie Wang