Node是什么
Node.js是一个基于Chrome V8引擎的JavaScript运行环境(runtime),Node不是一门语言是让js运行在后端的运行时,并且不包括javascript全集,因为在服务端中不包含DOM和BOM,Node也提供了一些新的模块例如http,fs模块等。Node.js 使用了事件驱动、非阻塞式 异步I/O的模型,使其轻量又高效并且Node.js 的包管理器 npm,是全球最大的开源库生态系统。
Node能够解决什么问题
- Node的首要目标是提供一种简单的,用于创建高性能服务器的开发工具
- Node在处理高并发,I/O密集场景有明显的性能优势 (高并发:
是指在同一时间并发访问服务器) - I/O密集指的是文件操作、网络操作、数据库,(相对的有CPU密集,CPU密集指的是逻辑处理运算、压缩、解压、加密、解密)
- Web主要场景就是接收客户端的请求读取静态资源和渲染界面,所以Node非常适合Web应用的开发。
进程和线程
进程是操作系统分配资源和调度任务的基本单位,线程是建立在进程上的一次程序运行单位,一个进程上可以有多个线程。
- js是单线程的。所以
node的主线程是单线程的 (node其实也是多线程的 setTimeout线程 ajax)
浏览器
- js线程
- ui线程
一般情况下 ui线程 渲染后 空闲下来 会执行js js线程和ui线程 是共享线程的 如果js是多线程的,不能同时两个线程 操作同一个DOM,这样会造成混乱。
webworker 进程
Worker可以开一个工作线程这个线程单独处理,但是要归主线程所管理 等到处理完成发送一个postMessage。
let worker = new Worker('./1.worker.js');
worker.postMessage(10000);
worker.onmessage = function (params){
console.log(e.data)
}'
console.log('main thread')
//1.worker.js
onmessage = function(e){
let r = e.data;
let sum = 0;
for(var i = 0;i<r;i++){
sum+=i;
}
postMessage(sum)
}
宏任务、微任务(执行时机是不一样的)(浏览器)
- 常见的宏任务 setTimeout(快) > setImmediate(慢,只兼容ie)、MessageChannel
- 微任务 promise(then)< process.nextTick、 MutationObserver
- 速度对比(快~慢:process.nextTick > promise > setTimeout > setImmediate)
- MessageChannel
// MessageChannel
let channel = new MessageChannel();
let port1 = channel.port1;
let port2 = channel.port2;
port1.postMessage('hello');
port2.onmessage = function(e){ // 异步的 vue就是宏任务
console.log(e.data);
}
- MutationObserver(兼容问题 微任务 vue.$nextTick的实现)
let observe = new MutationObserver(function(){
// 全部dom插入完成后再进这个方法
console.log('dom全部塞进去了')
})
observe.observe(div,{childList:true})
for(let i = 0;i<1000;i++){
div.appendChild(document.createElement('span'))
}
- 先会执行
栈中的内容,栈中内容执行后执行微任务,微任务清空后再执行宏任务,宏任务会在栈中执行。这样不停的循环就叫做事件环(event Loop) - 栈中存放基本数据类型,堆中存放引用数据类型。
- 浏览器事件环

同步和异步 阻塞和非阻塞
- 同步和异步指代的是
被调用而言的 - 阻塞和非阻塞针对的是
调用者而言的

repl (read eval print loop)(命令窗口)
let repl = require('repl');
let context = repl.start().context();
context.name = 'sss';
context.age = 9;
全局属性
- 全局属性 (global) 可以直接访问
console.log(this) // {}
function a(){
console.log(this) // global
}
a()
- console (标准输出代号1:前两个(log info)。错误输出代号2:3-4个(error warn))
- console.log('log') (process.stdout);
- console.info('info');
- console.error('error');
- console.warn('warn');
- console.time('a');
- console.timeEnd('a')
- console.assert((1+1)==3,'hello') // 断言 node中自带了一个模块 这个模块就叫assert()
- process
- argv 执行时的参数
- env 环境变量
- pid 编号
- exit 退出进程,退出自己
- chdir (change directory) 改变工作目录
- cwd (current working directory) 当前的工作目录
- stdout 标准输出 stderr 错误输出 stdin 标准输入
// process.argv
// node index.js --port 3000
let args = {};
// 前两个分别为node的目录和当前执行的文件的目录
process.argv.slice(2).forEach((item,idx)=>{
if(item.includes('--')){
args[item] = process.argv.slice(2)[idx+1]
}
})
console.log(args)
// process.env (环境变量:判断是开发环境还是线上环境)
// mac 通过export设置, windows通过 set设置。
// set NODE_ENV = dev && node index.js
let url;
if(process.env.NODE_ENV=='development'){
url = 'http://localhost:3000/api'
}else{
url = 'http://zzz.cn'
}
// 目录更改
process.chdir('..') // 更改当前的工作目录
process.cwd();
// 监听标准输入
process.stdin.on('data',function(data){
process.stdout.write(data.toString())
})
process.nextTick(function(){
console.log('nextTick')
})
- Node事件环
- timer 阶段 (定时器的回调)
- poll 阶段 (io的回调)等定时器到达后 执行对应的回调
- check 阶段 检查setImmediate
- 当阶段切换时 会执行微任务 把微任务清空

// node eventLoop 案例
// 1、setTimeout和setImmediate 顺序是不固定的,看node准备时间
setTimeout(function(){
console.log('setTimeout')
},0)
setImmediate(function(){
console.log('immediate')
})
// 2 微任务先执行 然后会将timer阶段的全部执行完 才会继续执行接下来的微任务
Promise.resolve().then(data=>{
console.log('promise1')
setTimeout(()=>{
console.log('time1')
},0)
})
setTimeout(()=>{
console.log('time2');
Promise.resolve().then(data=>{
console.log('promise2')
})
},0)
// promise1 time2 time1 promise2
//3 poll 阶段的下个阶段是check阶段
let fs = require('fs');
fs.readFile('./index.js','utf8',function(){
setTimeout(()=>{
console.log('timeout')
},0)
setImmediate(()=>{
console.log('setImmediate')
},0)
})
// setImmediate timeout
模块 (私有化,互相调用,方便代码维护)
- CMD规范 (就近依赖) seajs(针对的是浏览器)
- AMD规范 (依赖前置) requirejs(针对的是浏览器)
- commonjs node规范 自带了commonjs规范 (默认一个js就是一个模块)
- esmodule es6规范 (node里不支持)
commonjs
// 带路径的都是自己写的模块
// 不带路径的可能是node自带的还有可能是第三方的
// 1.js
module.exports = 'hello'
// 2.js
let str = require('./1.js');
console.log(str);
// node 代码最外层会有一个闭包 里面包括module,exports,require 全局属性 ,不是定义在global上的 但是可以直接使用
- 模块引用时会找到绝对路径
- 模块加载过会有缓存,有缓存直接拿出来用
- 缓存中存放的是路径和模块
- node实现模块化就是增加了一个闭包 加上一个自执行
(function(exports,require,module,__filename,__dirname){
// 文件的内容
})()
模块得分类
内置模块 (核心模块) fs、 path、 http、 加载速度是比较高的
- fs.accessSync 这个方法可以判断文件是否存在,如果不存在,会返回一个error
let fs = require('fs'); // readFile readFileSync
// 判断文件是否存在
fs.accessSync('./1.txt') // 默认是去查找一下 没有就出现异常
- path
let path = require("path");
// resolve join basename extname
// 解析就是把相对路径变成绝对路径
console.log(path.resolve('./2.txt,'a','b')) // resolve遇到/ 时会认为是根路径
console.log(path.join(__dirname,'./2.txt,'a','b'))
console.log(__dirname); // 绝对路径
console.log(__filename)
console.log(path.extname('1.a.b.js'))
console.log(path.basename('1.a.b.js','.b.js'))
console.log(path.posix.sep) // 路径分隔符
console.log(path.posix.delimiter) // 路径分隔符
- vm 沙箱
let vm = require('vm');
let a = 'ss'
vm.runInThisContext(`console.log(a)`) // 沙箱 这里取不到a 创建一个新的作用域
eval('console.log(a)') // eval 也是global上的属性 只不过是隐藏了 会受到当前作用域的影响。
文件模块 自己写的模块 加载速度慢并且是同步的(自己实现commonJs规范)
// 什么是commonjs规范
// 定义了如何导入模块 require
// 还定义了如何导出模块 module.exports
// 还定义了一个js就是一个模块
/*
1.模块的加载过程
1) a文件下 有一个a.js b文件夹下也有a.js 解析出一个绝对路径
2) 写的路径 可能没有后缀名 .js .json .node
3) 得到一个真实的加载路径(模块会缓存) 先去缓存中看一下这个文件是否存在,如果有返回缓存 没有创建一个模块
4) 得到文件的内容 加一个闭包,把内容塞进去
模块是有缓存的
模块中的this 是module.exports属性
模块定义的变量不能互相引用
exports是module.exports 的别名
模块默认返回的是module.exports 并不是exports
*/
let fs = require('fs');
let path = require('path');
let vm = require('vm');
function Module(p){
this.id = p; // 当前模块的标识
this.exports = {}; // 每个模块都有一个exports属性
this.loaded = false; // 模块是否加载完
}
Module.prototype.load = function(filepath){
// 判断加载的文件是json还是node或者是js
let ext = path.extname(filepath);
let content = Module._extensions[ext](this);
return content;
}
// 加载策略
Module.wrapper = ['(function(exports,require,module){','\n})']
Module._extensions = {
'.js':function(module){
// 读取js文件 增加一个闭包
let script = fs.readFileSync(module.id,'utf8');
let fn = Module.wrapper[0]+script+Module.wrapper[1];
vm.runInThisContext(fn).call(module.exports,module.exports,req,module);
return module.exports;
},
'.json':function(module){
return JSON.parse(fs.readFileSync(module.id,'utf8')); // 读取那个文件
},
'.node':''
}
Module._cacheModule = {} // 根据的是绝对路径进行缓存的
// 解析绝对路径的方法 返回一个绝对路径
Module._resolveFileName = function(moduleId){
let p = path.resolve(moduleId);
if(!path.extname(moduleId)){
let arr = Object.keys(Module._extensions);
for(let i = 0;i<arr.length;i++){
let file = p+arr[i];
try{
fs.accessSync(file); // 不存在会异常
return file
}catch(e){
console.log(e)
}
}
}else{
return p
}
}
function req(moduleId){
let p = Module._resolveFileName(moduleId);
if(Module._cacheModule[p]){
//模块存在(缓存) ,有直接返回
return Module._cacheModule[p].exports;
}
let module = new Module(p);
let content = module.load(p);
Module._cacheModule[p] = module;
module.exports = content;
return module.exports;
}
let a = req('./a.js')
第三方模块 安装 下载后不需要通过路径直接引用
npm init -y 先初始化一下 npm install mime
let mime = require('mime');
mime.getType('js');
包(需要有一个package.json的文件)的查找 ,如果文件夹下没有index.js,会默认找文件夹下的package.json的main入口
let str = require('./a')
{
"name":"xx",
"version":"1.0.0",
"main":"a.js" // 入口
}
- 第三方的模块可以根据node_modules查找,会不停的向上一级的node_modules查找
- npm link (命令行工具)
在package.json文件中创建bin对象,key为命令名字,value指向执行的文件(文件在bin目录下的文件)
#!/usr/bin/env node
console.log(11)
- 开发的时候 安装包有两种 1.开发的时候用 gulp webpack 上线时需要react jquery
- 如果只想安装项目依赖 可以采用npm install --production
- npm 发包
- 切换到官方源上
- npm addUser
- npm publish
- 发包成功
util
util.promisify把方法转化成promise
let {promisify} = require('util');
let fs = require('fs');
// promisify promisifyall bluebird
let read = promisify(fs.readFile);
// promisifyAll(fs);
read('1.txt','utf8').then(function(data){
console.log(data)
})
util.inherits继承
function A(){}
A.prototype.a = '1'
function B(){}
inherits(B,A) ; //只继承公有的方法 (Object.setPrototypeOf)
let b = new B();
console.log(b.a)
发布订阅 观察者模式(EventEmitter实现)
// 源码实现EventEmitter
function EventEmitter(){
this._events = {};
}
EventEmitter.defaultMaxListeners = 10;
EventEmitter.prototype.eventNames = function(){
return Object.keys(this._events);
}
// 获取最大的监听个数
EventEmitter.prototype.getMaxListeners = function(){
return this._count || EventEmitter.defaultMaxListeners;
}
EventEmitter.prototype.setMaxListeners = function(count){
this._count = count;
}
EventEmitter.prototype.on = function(type,callback){
// 如果实例上不存在则创建一个空对象
if(!this._events) this._events = Object.create(null)
// 如果当前不是newListener 方法 就需要让newListener的回调依次执行,并且传入类型
if(type!='newListener' && this._events['newListener'] && this._events['newListener'].length>0){
this._events['newListener'].forEach(fn=>{
fn(type)
})
}
if(this._events[type]){
this._events[type].push(callback)
}else{
this._events[type] = [callback]
}
if(this._events[type].length===this.getMaxListeners()){
console.warn('memory link detected')
}
}
EventEmitter.prototype.emit = function(type,...args){
if(this._events[type]){
this._events[type].forEach(fn=>fn(...args))
}
}
EventEmitter.prototype.listeners = function(type){
return this._events[type]
}
EventEmitter.prototype.removeAllListeners = function(type){
if(type){
return this._events[type] = []
}
this._events = {}
}
// 找到数组里对应的方法移除
EventEmitter.prototype.removeListener = function(type,callback){
if(this._events[type]){
this._events[type] = this._events[type].filter(fn=>{
return fn!=callback && fn.l !== callback;
})
}
}
EventEmitter.prototype.once = function(type,callback){
let wrap = (...args) => {
callback(...args);
this.removeListener(type,wrap)
}
wrap.l = callback; // 保存callback
this.on(type,wrap)
}
module.exports = EventEmitter