1、
在启动node项目服务时,可以为 node 传递一些参数,该参数可以在 process.argv 中获取,该属性是一个数组,第一项是:node.exe 的路径,第二项是本项目所在的路径;第三项往后是我们给 node 传递的参数:
const Koa = require('koa')
const app = new Koa()
console.log(process.argv)
app.listen()
利用process.argv我们可以做好多事情,比如:启动node服务时,端口可以在启动时自定义:
默认端口我们设置为:3000,启动服务自定义端口: node app.js 4000 或 node app.js 5000 等等。
// app.js
const Koa = require('koa')
const app = new Koa()
const port = process.argv[2] || 3000
app.listen(port, () => {
console.log('********************服务已开启********************')
})
2、
module.exports 和exports
源码中 会把:module.exports = exports,2个指向同一个内存地址,但是当一个js文件里同时有module.exports和exports导出时,module.exports的优先级大于exports,本质上是module.exports 在导出。
也可以这么理解module.exports是大哥,exports是小弟,当大哥module.exports = {} 等于一个对象时,相当于在另开辟了一个内存空间,此时的module.exports和exports指向的已经不是一个内存地址了, exports不管怎么导出或赋值都没有用了,大哥单独玩去了,不带小弟玩了。
3、
require(x)函数的查找规则
(1)如果是核心模块(http、path、fs等等),直接返回核心模块,并且停止查找。
(2)以 ./ 或 ../ 或 / 开头,在本地查找
1> 如果有后缀名,按照后缀名查找对应的文件
2> 如果没有后缀名,会按如下顺序查找:直接查找改文件x -> 查找 x.js 文件 -> 查找 x.json 文件 -> 查找 x.node 文件 ,如果没找到会报错:not fount
(3)直接是一个x,且x并不是一个核心模块,那么一般情况是一个第三方模块
如:require('test') 查找test模块,node会按照paths数组中的路径依次查找,如果未找到报错not found
4、
(1)模块在被第一次引入的时候,模块中的代码会被运行一次。
(2)模块被多次引入时,会被缓存,做种只执行(运行)一次,为什么只执行一次呢:每个模块对象module有一个loaded属性,记录当前木块是否被加载过,被加载过loaded为true。
5、
node采用的是深度优先算法: main -> aaa ->ccc -> ddd -> eee ->bbb
6、
在node中使用ES module
(1)方法一
限制条件有2个:
(1)node必须高版本:
(2)文件后缀必须为 .mjs 后缀
如:
// test1.mjs
let name = 'lxc'
export {
name
}
// test-a.mjs
import {name} from './test1.mjs'
console.log('name:', name) //输出:name: lxc
(2)方法二:
7、
npm install原理
从npm5开始,npm支持缓存策略,下边图揭示了npm install时的下载流程图:
npm install会根据package.json来下载项目中包依赖,开始先检查是否有package-lock.json文件,如果没有,分析包的依赖关系,有可能这个包换依赖别的包,多个包可能会产生相同依赖情况,从远程registry仓库下载包及包的依赖,此时下载下来的是压缩包,并且会把依赖包添加至电脑缓存中去,npm会自动把依赖包解压到node_modules下,此时会生成一个pack-lock.json文件,完成安装;如果有lock文件,首先会检查pack-lock.json中的包版本和package.json中的是否一致,不一致从新构建依赖关系,走上边流程,一致的话它会优先从本地缓存中查找,如果从缓存中找到依赖包,会获取缓存中的压缩文件,把压缩文件解压到node_modules下,完成安装。
8、
接着上边7来说,如果查看npm在电脑上的缓存路径命令如下:
npm config get cache
下边是我电脑上依赖包的缓存路径:
9、
清除电脑上的npm包缓存:
npm cache clean
10、
yarn 新npm包管理工具,早期npm不太好用(安装速度慢、版本依赖乱)
安装yarn:
npm i yarn -g
yarn 跟 npm对比图:
11、
进程、线程
进程:我们可以认为,启动一个应用程序,就会默认启动一个进程(也可能是多个进程);
线程:每一个进程中,都会启动一个线程用来执行程序中的代码,这个线程称之为主线程;
所以,我们也可以说进程是线程的容器。
一个形象的例子:操作系统类似于一个工厂,工厂中有很多车间,车间就是进程,而一个车间可能有一个以上的工人在工作,这个工人就是线程。
javascript 执行过程
上边代码执行过程:
定义变量name -》
执行log函数,函数会被压入调用栈中 -》
调用完,log函数出栈 -》
调用bar函数,被压入到调用栈中 -》
执行未结束,因为里边调用sum函数,sum被压入栈中 -》
sum执行完,出栈,-》
bar获取到结果,出栈,res结果获取到,最后log函数压入到调用栈中,执行完,出栈;
宏任务和微任务:
事件循环中并非只维护着一个队列,事实上是由2个队列:
DOM监听、ajax、定时器、setInterval、UIRendering都会有一个回调函数,这个回调函数就是一个任务,这个任务叫macrotask:宏任务,宏任务会被加入到红队列里边;
Promise().then() 的回调then 、Mutation Observer API、产生的是一个微任务,微任务会加入到微任务队列。
浏览器会先去执行微任务队列,全部执行完之后,然后再去执行宏任务队列,当执行完一个宏任务,执行下一个宏任务之前,都会查看微任务队列是否有任务需要执行(也就是宏任务执行之前必须保证微任务队列是空的),有的话先去执行微任务,全部执行完,然后再去执行宏任务,如此反复,直到全部执行完。
来2道面试题:
(1)
执行顺序(下边分析只是做个记录,仅供个人参考):
setTimeout 宏任务,直接放到宏任务队列,往下到2 Promise,会直接打印:'4',碰到resolve,会把2下边的then回调放到微任务队列中,接着看到3下边setTimeout,直接放到宏任务队列,往下看到4下边有打印,log会压入到调用栈中执行,输出'7', log函数出栈,往下,5下边queueMicrotask,放到微任务队列,往下,6下边又是Promise,把then回调放到微任务队列,此时会开始把微任务队列中的回调挨个压入到调用栈中执行,清空微任务队列后,会执行宏任务队列,执行1下边的setTimeout时,要注意:先打印1,下边碰到Promise,又会把then回调放到微任务队列,此时1下边的setTimeout已经执行完了,紧接着它会执行微任务队列中的回调,结果还是一个Promise,继续把Promise后边then回调放到微任务里边,继续执行,打印后边的 3,然后打印 2,最后才执行3下边的setTimeout ,输出:6
// 1
setTimeout(() => {
console.log('1')
new Promise((resolve) => {
resolve();
}).then(() => {
new Promise((resolve) => {
resolve()
}).then(() => {console.log('2')})
console.log('3')
})
})
// 2
new Promise(resolve => {
console.log('4')
resolve()
}).then(() => {console.log('5')})
// 3
setTimeout(() => {console.log('6')})
// 4
console.log('7')
// 5
queueMicrotask(() => {console.log('8')})
// 6
new Promise(resolve => {
resolve()
}).then(() => {console.log('9')})
// 正确输出结果为:4 -> 7 -> 5 -> 8 -> 9 -> 1 -> 3 -> 2 -> 6
(2)
下边这个不分析了,async中的await会阻塞线程,await后边的代码,相当于.then回调,所以a函数中的await执行完之后,后边的console.log('2')会被放到微任务队列中去!!!
async function a() {
console.log('1')
await b();
console.log('2')
}
async function b() {
console.log('3')
}
console.log('4')
setTimeout(() => {
console.log('5')
}, 0)
a()
new Promise((resolve) => {
console.log('6')
resolve()
}).then(() => {
console.log('7')
})
console.log('8')
//正确输出结果:4 -> 1 -> 3 -> 6 -> 8 -> 2 -> 7 -> 5
12、
发布订阅者模式:
class EventEmitter {
constructor() {
this._events = {}
}
on(eventName, callback) {
if(!this._events[eventName]) {
this._events[eventName] = [callback]
}else {
this._events[eventName].push(callback)
}
}
emit(eventName) {
if(this._events[eventName] && this._events[eventName].length) {
this._events[eventName].forEach(cb => cb())
}else {
console.log('not find events!')
}
}
once(eventName, callback) {
let fn = () => {
callback()
this.removeEvent(eventName, fn)
}
this.on(eventName, fn)
}
removeEvent(eventName, callback) {
if(this._events[eventName]) {
this._events[eventName] = this._events[eventName].filter(cb => cb !== callback)
}
}
}
const event = new EventEmitter()
function ev() {
console.log('触发了2')
}
// event.on('data', ev) // 绑定事件
// event.emit('data') // 触发事件
event.once('data', ev) // 只会绑定一次事件,当事件被触发之后,该事件会被删除
event.emit('data') // 触发事件