performance使用

0c9f29c9ce8c36ad137763278bab67fb
FP:First Paint
FCP:First Contentful Paint
DCL:DocumentContentLoaded Event HTML、CSS、JavaScript解析完成时触发
L:onload Event 页面其他资源如图片、视频等加载完成后触发,一般在DCL后触发
FMP:First Meaningful Paint
分别对应其左侧的虚线
09ed2fb79630af49994c0d694b08b7c8

发表在 浏览器 | 留下评论

nodejs高并发演示

压测脚本: ab -n 100 -c 100 http://127.0.0.1:8000/
http://127.0.0.1:8000/代码如下:
A).同步阻塞

//index.js
const http = require('http');
let result = 0;
http.createServer((req, res) => {
  let start = new Date();
  console.log(`request arrive--${start}`);
  var waitUntil = new Date(new Date().getTime() + 10 * 1000);
  while(waitUntil > new Date()){}
  console.log(`setTimeout--${result}-response end--${new Date()}`);
}).listen(8000);

执行node index.js
结果:
request arrive–Wed Apr 01 2020 11:05:40 GMT+0800 (GMT+08:00)
setTimeout–0-response end–Wed Apr 01 2020 11:05:50 GMT+0800 (GMT+08:00)
request arrive–Wed Apr 01 2020 11:05:50 GMT+0800 (GMT+08:00)
setTimeout–0-response end–Wed Apr 01 2020 11:06:00 GMT+0800 (GMT+08:00)
request arrive–Wed Apr 01 2020 11:06:00 GMT+0800 (GMT+08:00)
setTimeout–0-response end–Wed Apr 01 2020 11:06:10 GMT+0800 (GMT+08:00)
request arrive–Wed Apr 01 2020 11:06:10 GMT+0800 (GMT+08:00)
…..

能看到请求被阻塞了,需要等待

B).改成异步非阻塞

//index.js
const http = require('http');
const request = require('request');
let result = 0;
http.createServer((req, res) => {
  let start = new Date();
  console.log(`request arrive--${start}`);
  request('http://v.qq.com/', { json: true }, (err, ret, body) => {
    if (err) { return console.log(err); }
    console.log(`setTimeout--${result}-response end--${new Date()}`);
    res.end(`${result} end--${new Date()}`);
  });
}).listen(8000);

执行node index.js
结果如下:
request arrive–Wed Apr 01 2020 11:08:48 GMT+0800 (GMT+08:00)
request arrive–Wed Apr 01 2020 11:08:48 GMT+0800 (GMT+08:00)
request arrive–Wed Apr 01 2020 11:08:48 GMT+0800 (GMT+08:00)
request arrive–Wed Apr 01 2020 11:08:48 GMT+0800 (GMT+08:00)
request arrive–Wed Apr 01 2020 11:08:48 GMT+0800 (GMT+08:00)
request arrive–Wed Apr 01 2020 11:08:48 GMT+0800 (GMT+08:00)
request arrive–Wed Apr 01 2020 11:08:48 GMT+0800 (GMT+08:00)
request arrive–Wed Apr 01 2020 11:08:48 GMT+0800 (GMT+08:00)
request arrive–Wed Apr 01 2020 11:08:48 GMT+0800 (GMT+08:00)
request arrive–Wed Apr 01 2020 11:08:48 GMT+0800 (GMT+08:00)
request arrive–Wed Apr 01 2020 11:08:48 GMT+0800 (GMT+08:00)
……
Error: getaddrinfo ENOTFOUND v.qq.com
at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:60:26) {
errno: ‘ENOTFOUND’,
code: ‘ENOTFOUND’,
syscall: ‘getaddrinfo’,
hostname: ‘v.qq.com’
}
setTimeout–0-response end–Wed Apr 01 2020 11:09:16 GMT+0800 (GMT+08:00)
setTimeout–0-response end–Wed Apr 01 2020 11:09:17 GMT+0800 (GMT+08:00)
setTimeout–0-response end–Wed Apr 01 2020 11:09:18 GMT+0800 (GMT+08:00)
setTimeout–0-response end–Wed Apr 01 2020 11:09:18 GMT+0800 (GMT+08:00)
setTimeout–0-response end–Wed Apr 01 2020 11:09:18 GMT+0800 (GMT+08:00)
setTimeout–0-response end–Wed Apr 01 2020 11:09:18 GMT+0800 (GMT+08:00)
setTimeout–0-response end–Wed Apr 01 2020 11:09:19 GMT+0800 (GMT+08:00)
….
可以看到高并发的效果,每个请求都被处理了

发表在 NodeJs | 留下评论

es6模块

本文介绍浏览器环境的es6模块
浏览器环境中,type=”module”自动标志这个内联或者外联脚本是一个模块。一般模块脚本建议后缀为.mjs,这也与nodejs的保持一致,nodejs中es6的模块化特性只支持.mjs后缀的文件(nodejs9+ :node –experimental-modules 1.mjs).
es6的模块特性:
1.天然自带异步特性,即自带defer,es6模块按顺序先后执行

<script type="module" src="./2.js"></script>
<script type="module">
    console.log('4.type-module.module');
</script>
<script defer>
    console.log('1.defer script');
</script>
<script>
    console.log('2.script');
</script>
<script defer src="./3.js"></script>

输出1.defer script后外链的输出2.script 后 2.js(外链js模块由于顺序靠前,也在非外链js模块前执行,所以能保证顺序)然后4.type-module.module 最后3.js.
内联script没有defer属性,设置defer属性无效,无视defer。
2.支持async,异步加载脚本,区别于defer的保证执行顺序,其谁先加载好谁先执行。
3.同一个js只会加载且执行一次
4.总是CORS跨域请求,所以非同源会报错,需要模块资源服务端配置Access-Control-Allow-Origin
5.模块脚本默认不发送cookie等。需要设置crossorigin=”use-credentials”来携带cookie。API接口会带cookie不用修改
6.天然严格模式
7.静态import和动态import:静态import在首次加载时会把全部资源都加载下来。而动态import按需加载
a).静态import

<script async type="module">
import * as a from './1.js'
</script>

b).动态import(此import非函数,返回promise)

<script>
setTimeout(() => {
    import('./1.js').then(function (a) {
        console.log('1---', a)
    });
}, 5000);
</script>

7.import必须是绝对地址或者相对地址。
Uncaught (in promise) TypeError: Failed to resolve module specifier “3.js”. Relative references must start with either “/”, “./”, or “../”.

参考文章:
万岁,浏览器原生支持ES6 export和import模块啦!

发表在 JavaScript, 浏览器, 规范 | 留下评论

es6与JavaScript关系

javascript是一种语言、HTML也是一种语言,而浏览器有解析JavaScript和HTML的引擎,所以其能运行这两种语言。
那es6又是什么呢?
这就要涉及到历史知识了,先有JavaScript,后来JavaScript的创造公司把JavaScript委托给ECMA大公司,大公司来制定标准,即ECMAScript规范,es规范,各浏览器厂商根据规范来实现,这个实现就是JavaScript。其实都是代号而已,es6是ECMAScript的2015年规范,有的浏览器遵循此规范进行了实现,而有的浏览器比较倔强还未实现或者部分实现了。

浏览器原生支持JavaScript。所以JavaScript能在浏览器运行,但是浏览器对js的兼容性不同,导致有些js代码在某些不兼容浏览器上不能正确使用,比如attachEvent和attachEventLister这两个API就分别对应IE和其他浏览器。

那么es6是如何被应用到浏览器的呢?
刚才说了,不是所有的浏览器都支持es6,网上给了两种实践:
第一种:es6被babel编译成es5用;因为es5基本都被现在浏览器实现了
第二种:script type=module与nomodule分别对应兼容或者不兼容es6时的解决方案
第一种比较好理解,我们看第二种。

<script type="module" src="module.mjs"></script>
<script nomodule src="fallback.js"></script>

对于支持ES6模块导入的浏览器,自然也支持原生的nomodule属性,此时fallback.js是忽略的;但是,对于不支持的老浏览器,无视nomodule、和type=module,此时module.mjs不执行,而fallback.js就会执行,于是浏览器全兼顾。
参考文章:
译」ES5, ES6, ES2016, ES.Next: JavaScript 的版本是怎么回事?
详解JavaScript与ECMAScript的区别
万岁,浏览器原生支持ES6 export和import模块啦!

发表在 JavaScript | 留下评论

捕获和冒泡演示例子

A

B

C


冒泡:c->b->a
捕获:a->b->c

发表在 JavaScript | 留下评论

express框架 使用session 登录

express-session使用session登录,session可以存入内存,或者存入数据库,如存入MongoDB,需要connect-mongo

_.extend(conf, {
    store: new MongoStore({
        url: 'mongodb://127.0.0.1:27017/dbname'
    })
});

express封装了request和response对象。

// session存储
request.session = {
    username: 'xiaoming'
};

request每次携带cookie中的connect.sid,通过这个ID去MongoDB数据库里或者内存里读出session内容。

发表在 NodeJs | 留下评论

nodejs

阻塞是指在 Node.js 程序中,其它 JavaScript 语句的执行,必须等待一个非 JavaScript 操作完成。这是因为当 阻塞 发生时,事件循环无法继续运行 JavaScript。

在 Node.js 中,JavaScript 由于执行 CPU 密集型操作,而不是等待一个非 JavaScript 操作(例如 I/O)而表现不佳,通常不被称为 阻塞。在 Node.js 标准库中使用 libuv 的同步方法是最常用的 阻塞 操作。原生模块中也有 阻塞 方法。

在 Node.js 标准库中的所有 I/O 方法都提供异步版本,非阻塞,并且接受回调函数。某些方法也有对应的 阻塞 版本,名字以 Sync 结尾。

我们说nodejs是高并发的,nodejs并发是什么概念呢?

nodejs 无中间件概念,中间件的概念是框架引入的,比如express。express的中间件更类似于插件plugin的概念。

事件循环和执行JavaScript在一个线程里,JavaScript是单线程的。
nodejs调度有阻塞调度和非阻塞调度。
对javascript阻塞的概念:JavaScript的执行必须等一个非JavaScript的操作执行完成,譬如JavaScript执行必须等读取文件结束才能继续执行就是阻塞操作。
而nodejs被称为非阻塞,是因为其JavaScript执行一般不受非JavaScript操作的阻塞,而是受限于CPU密集型操作。

JavaScript是单线程的,定时器是如何工作的呢?

首先 setTimeout 和 setInterval 都不是ECMAScript规范或者任何JavaScript实现的一部分。它是由浏览器实现,并且在不同的浏览器也会有所差异。定时器也可以由 Nodejs 运行时本身实现.
setTimeout和setInterval的运行机制,是将指定的代码移出本轮事件循环,等到下一轮事件循环,再检查是否到了指定时间。如果到了,就执行对应的代码;如果不到,就继续等待。
参考文章:从零开始再学 JavaScript 定时器

除了setTimeout和setInterval这两个方法,Node.js还提供了另外两个与”任务队列”有关的方法:process.nextTick和setImmediate。它们可以帮助我们加深对”任务队列”的理解。

process.nextTick方法可以在当前”执行栈”的尾部—-下一次Event Loop(主线程读取”任务队列”)之前—-触发回调函数。也就是说,它指定的任务总是发生在所有异步任务之前。setImmediate方法则是在当前”任务队列”的尾部添加事件,也就是说,它指定的任务总是在下一次Event Loop时执行,这与setTimeout(fn, 0)很像

参考文章:JavaScript 运行机制详解:再谈Event Loop

官方认为process.nextTick被滥用了,所以提出了setImmediate
参考文章:event-loop-timers-and-nexttick

Node.js 通过事件循环机制(初始化和回调)的方式运行 JavaScript 代码,并且提供了一个线程池处理诸如文件 I/O 等高成本的任务。Node 可伸缩性的秘诀在于它仅使用了极少数的线程就可以处理大量客户端连接。 在 Node 中,有两种类型的线程:一个事件循环线程(也被称为主循环,主线程,事件线程等)。另外一个是在工作线程池里的 k 个工作线程(也被称为线程池)。
Node 使用事件驱动机制:它有一个事件轮询线程负责任务编排,和一个专门处理繁重任务的工作线程池。
参考文章:dont-block-the-event-loop

发表在 JavaScript, NodeJs | 留下评论

javascript 定时器setTimeout

定时器

const s = new Date().getSeconds();

setTimeout(function() {
  // 输出 "2",表示回调函数并没有在 500 毫秒之后立即执行
  console.log("Ran after " + (new Date().getSeconds() - s) + " seconds");
}, 500);

while(true) {
  if(new Date().getSeconds() - s >= 20) {
    console.log("Good, looped for 2 seconds");
    break;
  }
}

Good, looped for 2 seconds
Ran after 20 seconds
定时器异步操作,必须等主线程执行完才会执行。

参考:
EventLoop

发表在 JavaScript | 留下评论

setTimeout最小延迟时间

var start = new Date().getTime();
function foo () {
    var end = new Date().getTime();
    console.log(&quot;Execution time1-foo: &quot;, (end - start));
}
setTimeout(foo);setTimeout(foo, 0);

输出两次: Execution time1-foo: 1

HTML5规范规定最小延迟时间不能小于4ms,即x如果小于4,会被当做4来处理。 不过不同浏览器的实现不一样,比如,Chrome可以设置1ms,IE11/Edge是4ms。

发表在 JavaScript | 留下评论

【转载】nodejs 与 JavaScript 事件循环的差异

浏览器篇已经对事件循环机制和一些相关的概念作了详细介绍,但主要是针对浏览器端的研究,Node环境是否也一样呢?先看一个demo:
setTimeout(()=>{
console.log(‘timer1’)
Promise.resolve().then(function(){
console.log(‘promise1’)
})
},0)
setTimeout(()=>{
console.log(‘timer2’)
Promise.resolve().then(function(){
console.log(‘promise2’)
})
},0)
肉眼编译运行一下,蒽,在浏览器的结果就是下面这个了,道理都懂,就不累述了。
timer1
promise1
timer2
promise2
那么Node下执行看看,咦。。。奇怪,跟浏览器的运行结果并不一样~
timer1
timer2
promise1
promise2
例子说明,浏览器和 Node.js 的事件循环机制是有区别的,一起来看个究竟吧~

Node.js的事件处理

Node.js采用V8作为js的解析引擎,而I/O处理方面使用了自己设计的libuv,libuv是一个基于事件驱动的跨平台抽象层,封装了不同操作系统一些底层特性,对外提供统一的API,事件循环机制也是它里面的实现,核心源码参考
int uv_run(uv_loop_t* loop, uv_run_mode mode) {
int timeout;
int r;
int ran_pending;
r = uv__loop_alive(loop);
if (!r)
uv__update_time(loop);
while (r != 0 && loop->stop_flag == 0) {
uv__update_time(loop);
// timers阶段
uv__run_timers(loop);
// I/O callbacks阶段
ran_pending = uv__run_pending(loop);
// idle阶段
uv__run_idle(loop);
// prepare阶段
uv__run_prepare(loop);
timeout = 0;
if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT)
timeout = uv_backend_timeout(loop);
// poll阶段
uv__io_poll(loop, timeout);
// check阶段
uv__run_check(loop);
// close callbacks阶段
uv__run_closing_handles(loop);
if (mode == UV_RUN_ONCE) {
uv__update_time(loop);
uv__run_timers(loop);
}
r = uv__loop_alive(loop);
if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)
break;
}
if (loop->stop_flag != 0)
loop->stop_flag = 0;
return r;
}
根据Node.js官方介绍,每次事件循环都包含了6个阶段,对应到 libuv 源码中的实现,如下图所示
添加图片
 
  • timers 阶段:这个阶段执行timer(setTimeout、setInterval)的回调
  • I/O callbacks 阶段:执行一些系统调用错误,比如网络通信的错误回调
  • idle, prepare 阶段:仅node内部使用
  • poll 阶段:获取新的I/O事件, 适当的条件下node将阻塞在这里
  • check 阶段:执行setImmediate()的回调
  • close callbacks 阶段:执行socket的close事件回调
我们重点看timers、poll、check这3个阶段就好,因为日常开发中的绝大部分异步任务都是在这3个阶段处理的。

timers 阶段

timers 是事件循环的第一个阶段,Node 会去检查有无已过期的timer,如果有则把它的回调压入timer的任务队列中等待执行,事实上,Node 并不能保证timer在预设时间到了就会立即执行,因为Node对timer的过期检查不一定靠谱,它会受机器上其它运行程序影响,或者那个时间点主线程不空闲。比如下面的代码,setTimeout()和setImmediate()的执行顺序是不确定的。
setTimeout(()=>{
console.log(‘timeout’)
},0)
setImmediate(()=>{
console.log(‘immediate’)
})
但是把它们放到一个I/O回调里面,就一定是setImmediate()先执行,因为poll阶段后面就是check阶段。

poll 阶段

poll 阶段主要有2个功能:
  • 处理 poll 队列的事件
  • 当有已超时的 timer,执行它的回调函数
even loop将同步执行poll队列里的回调,直到队列为空或执行的回调达到系统上限(上限具体多少未详),接下来even loop会去检查有无预设的setImmediate(),分两种情况:
  1. 若有预设的setImmediate(), event loop将结束poll阶段进入check阶段,并执行check阶段的任务队列
  2. 若没有预设的setImmediate(),event loop将阻塞在该阶段等待
注意一个细节,没有setImmediate()会导致event loop阻塞在poll阶段,这样之前设置的timer岂不是执行不了了?所以咧,在poll阶段event loop会有一个检查机制,检查timer队列是否为空,如果timer队列非空,event loop就开始下一轮事件循环,即重新进入到timer阶段。

check 阶段

setImmediate()的回调会被加入check队列中, 从event loop的阶段图可以知道,check阶段的执行顺序在poll阶段之后。

小结

  • event loop 的每个阶段都有一个任务队列
  • 当 event loop 到达某个阶段时,将执行该阶段的任务队列,直到队列清空或执行的回调达到系统上限后,才会转入下一个阶段
  • 当所有阶段被顺序执行一次后,称 event loop 完成了一个 tick
讲得好有道理,可是没有demo我还是理解不全啊,憋急,now!
constfs=require(‘fs’)
fs.readFile(‘test.txt’,()=>{
console.log(‘readFile’)
setTimeout(()=>{
console.log(‘timeout’)
},0)
setImmediate(()=>{
console.log(‘immediate’)
})
})
执行结果应该都没有疑问了
readFile
immediate
timeout

Node.js 与浏览器的 Event Loop 差异

回顾上一篇,浏览器环境下,microtask的任务队列是每个macrotask执行完之后执行。
添加图片
 浏览器端
而在Node.js中,microtask会在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行microtask队列的任务。
添加图片
Node.js端

demo回顾

回顾文章最开始的demo,全局脚本(main())执行,将2个timer依次放入timer队列,main()执行完毕,调用栈空闲,任务队列开始执行;
添加图片
Node.js下的处理过程
首先进入timers阶段,执行timer1的回调函数,打印timer1,并将promise1.then回调放入microtask队列,同样的步骤执行timer2,打印timer2;
至此,timer阶段执行结束,event loop进入下一个阶段之前,执行microtask队列的所有任务,依次打印promise1、promise2。
对比浏览器端的处理过程:
添加图片
 Browser下的处理过程
process.nextTick() VS setImmediate()
In essence, the names should be swapped. process.nextTick() fires more immediately than setImmediate()
来自官方文档有意思的一句话,从语义角度看,setImmediate()应该比process.nextTick()先执行才对,而事实相反,命名是历史原因也很难再变。
process.nextTick()会在各个事件阶段之间执行,一旦执行,要直到nextTick队列被清空,才会进入到下一个事件阶段,所以如果递归调用process.nextTick(),会导致出现I/O starving(饥饿)的问题,比如下面例子的readFile已经完成,但它的回调一直无法执行:
constfs=require(‘fs’)
conststarttime=Date.now()
letendtime
fs.readFile(‘text.txt’,()=>{
endtime=Date.now()
console.log(‘finish reading time: ‘,endtime-starttime)
})
letindex=0
functionhandler(){
if(index++>=1000)return
console.log(`nextTick ${index}`)
process.nextTick(handler)
// console.log(`setImmediate ${index}`)
// setImmediate(handler)
}
handler()
process.nextTick()的运行结果:
nextTick 1
nextTick 2
……
nextTick 999
nextTick 1000
finish reading time: 170
替换成setImmediate(),运行结果:
setImmediate 1
setImmediate 2
finish reading time: 80
……
setImmediate 999
setImmediate 1000
这是因为嵌套调用的setImmediate()回调,被排到了下一次event loop才执行,所以不会出现阻塞。

总结

  1. Node.js 的事件循环分为6个阶段
  2. 浏览器和Node 环境下,microtask任务队列的执行时机不同
    • Node.js中,microtask在事件循环的各个阶段之间执行
    • 浏览器端,microtask在事件循环的macrotask执行完之后执行
  3. 递归的调用process.nextTick()会导致I/O starving,官方推荐使用setImmediate()
[参考资料]

发表在 JavaScript, NodeJs | 留下评论