Node基础之总体概览

590 阅读8分钟

Node是什么

Node.js是一个基于 Chrome V8 引擎的JavaScript运行环境(runtime),Node不是一门语言是让js运行在后端的运行时,并且不包括javascript全集,因为在服务端中不包含DOMBOM,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