Nodejs
计算机知识思维导图:Nodejs。网页展示前三层结构,可在线查看完整脑图并下载 GoMind 文件。
2026-08-25
## Nodejs ### Nodejs #### 异步流程控制 ##### Promise ##### co模块 ##### async模块 ##### async/await #### apply/call ##### apply ##### call ##### bind ##### 绑定后立即执行 ##### 绑定后非立即执行 #### 非IO异步Api ##### settimeout ##### setInterval ##### setImmediate ##### process.nextTick ##### 内部由红黑树实现,时间复杂度为O(lg(n)) ##### 优先级对比: process.nextTick > setImmediate > setTimeout = setInterval , idle观察者优于IO观察者,IO观察者优于check观察者, process.nextTick 属于idle观察者,setImmediate属于check观察者 #### 闭包 ##### 用处 ##### 注意 #### 常用框架 ##### express ##### koa #### 优先级对比 ##### 同步代码(包括promise的构造函数) -> promise.then -> setTimeout , 实际上promise.then内部会根据系统来判断,若系统支持setImmediate则直接执行该函数,否则执行process.nextTick > 仅展示前三层结构;请在线查看完整脑图或下载 GoMind 文件。
Nodejs
Nodejs
对象的状态不受外界影响
一旦状态改变就不会再变
两大特点
pending
fullfilled
rejected
三种状态
一旦建立就会立即执行,无法中途取消
内部抛出的错误无法反应到外部,同时异常堆栈没有记录出错的具体位置
当处于pending状态时,无法得知目前进展到哪个阶段
若业务逻辑过多会导致有过多的then及嵌套,导致代码可读性变差
调试不方便
缺点
Promise
promise
thunks
array
objects
generators
generator functions
yield后面的数据类型
并发异步
// 数组的写法
co(function* () {
var res = yield [
Promise.resolve(1),
Promise.resolve(2)
];
console.log(res);
}).catch(onerror);
// 对象的写法
co(function* () {
var res = yield {
1: Promise.resolve(1),
2: Promise.resolve(2),
};
console.log(res);
}).catch(onerror);co模块
基于 ES6 的 generator 和 iterator实现 将生成器放到co方法参数中执行。 即 generator自动执行函数库。
模块导出分析
由于现在js使用的范围非常广,又有后端nodejs,又有前端js,需要适应不同的导入操作。这段代码就可以非常完美地做到对于不同导入方式的支持,包括:
nodejs中的require语法
RequireJs的导入方式
html中script标签的导入方式
// Node.js
if (typeof module === 'object' && module.exports) {
module.exports = async;
}
// AMD / RequireJS
else if (typeof define === 'function' && define.amd) {
define([], function () {
return async;
});
}
// included directly via <script> tag
else {
root.async = async;
}async模块
会返回Promise.resolve(undefined)
无返回值
返回Promise.resolve(直接量)
直接量
有返回值
async
await
async/await
异步流程控制
apply
func.apply(this, [arg1, arg2])
call
func.call(this, arg1, arg2);
bind
绑定后返回一个函数,待后续调用。 多次 bind() 是无效的,更深层次的原因, bind() 的实现,相当于使用函数在内部包了一个 call / apply ,第二次 bind() 相当于再包住第一次 bind() ,故第二次以后的 bind 是无法生效的。
绑定后立即执行
绑定后非立即执行
apply/call
都是为了改变函数执行上下文
1. 改变this指针,第一个参数都是this要指向的对象,也就是想指定的上下文;
2. 借用其他对象的方法
下面就借用一道面试题,来更深入的去理解下 apply 和 call 。
定义一个 log 方法,让它可以代理 console.log 方法,常见的解决方法是:
function log(msg) {
console.log(msg);
}
log(1); //1
log(1,2); //1
上面方法可以解决最基本的需求,但是当传入参数的个数是不确定的时候,上面的方法就失效了,这个时候就可以考虑使用 apply 或者 call,注意这里传入多少个参数是不确定的,所以使用apply是最好的,方法如下:
function log(){
console.log.apply(console, arguments);
};
log(1); //1
log(1,2); //1 2
接下来的要求是给每一个 log 消息添加一个"(app)"的前辍,比如:
log("hello world"); //(app)hello world
该怎么做比较优雅呢?这个时候需要想到arguments参数是个伪数组,通过 Array.prototype.slice.call 转化为标准数组,再使用数组方法unshift,像这样:
function log(){
var args = Array.prototype.slice.call(arguments);
args.unshift('(app)');
console.log.apply(console, args);
};回调函数中的this指向window对象
setTimeout不止两个参数,后面的参数可作为回调参数的入参
HTML5中已经将最小执行时间统一为4ms
所谓的立即执行 0
来看一个网上很流行的例子:
1. document.querySelector('#one input').onkeydown = function() {
document.querySelector('#one span').innerHTML = this.value;
};
2. document.querySelector('#second input').onkeydown = function() {
setTimeout(function() {
document.querySelector('#second span').innerHTML = document.querySelector('#second input').value; }, 0);
};
第一个仅会输出最后一个字符前面的字符,后漏掉最后输入的那个字符,而第二个加了settimeout会准确输出settimeout
采用定时器需要动用红黑树。
setTimeout(function(){
// statement
}, 0);
上面的代码表示立即执行。
本意是立刻执行调用函数,但事实上,上面的代码并不是立即执行的,这是因为setTimeout有一个最小执行时间,当指定的时间小于该时间时,浏览器会用最小允许的时间作为setTimeout的时间间隔,也就是说即使我们把setTimeout的延迟时间设置为0,被调用的程序也没有马上启动。
不同的浏览器实际情况不同,IE8和更早的IE的时间精确度是15.6ms。不过,随着HTML5的出现,在高级版本的浏览器(Chrome、ie9+等),定义的最小时间间隔是不得低于4毫秒,如果低于这个值,就会自动增加,并且在2010年及之后发布的浏览器中采取一致。
所以说,当我们写为 setTimeout(fn,0) 的时候,实际是实现插队操作,要求浏览器“尽可能快”的进行回调,但是实际能多快就完全取决于浏览器了。
著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。
原文: http://ghmagical.com/article/page/id/H61NOVU0RZ9Y © ghmagical.com著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。
原文: http://ghmagical.com/article/page/id/H61NOVU0RZ9Y © ghmagical.com实现与settimeout大体一致,只是该方法多了重复执行的过程
setInterval
内部由链表实现
setImmediate
内部由数组实现,时间复杂度为O(1)
process.nextTick
内部由红黑树实现,时间复杂度为O(lg(n))
优先级对比: process.nextTick > setImmediate > setTimeout = setInterval ,
idle观察者优于IO观察者,IO观察者优于check观察者,
process.nextTick 属于idle观察者,setImmediate属于check观察者
非IO异步Api
读取函数内部变量
让这些变量始终保持在内存中
栗子1:
function f1(){
var n=999;
nAdd=function(){n+=1}
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000
原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
栗子2:
for( var i = 0; i < 5; i++ ) {
setTimeout(() => {
console.log( i );
}, 1000 * i)
}
// 结果为5个5,每秒输出一个
根据作用域的工作原理,实际情况是尽管循环中的五个函数是在各个迭代中分别定义的,
但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个 i。
栗子3:
for (var i = 1; i <= 5; i++) {
let j = i; // 输出结果正常
setTimeout(function timer() {
console.log(j);
},j*1000);
}
栗子4:
for (var i = 1; i <= 5; i++) {
var j = i; // 输出结果没变
setTimeout(function timer() {
console.log(j);
},j*1000);
}
栗子5:
for( var i = 0; i < 5; i++ ) {
((j) => {
setTimeout(() => {
console.log( j );
}, 1000 * j)
})(i)
}
栗子6:
function test(){
for (let i=0; i<5; i++) { // 正常
setTimeout( function timer() {
console.log(new Date(),i);
}, i*1000 );
}
// console.log("end",new Date(),i); //因为变量作用域的问题,这里会报i 不存在,未声明
}用处
由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,
所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。
解决方法是,在退出函数之前,将不使用的局部变量全部删除。
闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
注意
闭包
闭包就是能够读取其他函数内部变量的函数。
栗子1:
getNameFunc返回的匿名函数不属于getNameFunc的成员变量,也就无法访问其父级的变量。
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()());
栗子2:
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()());express
koa
常用框架
同步代码(包括promise的构造函数) -> promise.then -> setTimeout ,
实际上promise.then内部会根据系统来判断,若系统支持setImmediate则直接执行该函数,否则执行process.nextTick
优先级对比