Node简介,浏览器事件环机制

Node简介

Node能够解决什么问题?

  • 能够建立一种高性能服务器的开发工具,所谓高性能仅指在某一方面性能高,在web开发中,解决一些并发服务器的问题,主要应用场景是web
  • 对比
  1. java和php 处理计算,压缩,解压,加密,解压运算;多线程
  2. Node 非阻塞异步i/o 操做文件,网络操做,数据库操做,处理高并发

Node是什么

Node不是一门语言,只是运行环境(runtime),只是提供了基于V8引擎的运行时(运行的环境,如console),也不是js,不包含BOm,Dom,,为了能实现服务端的功能新增了许多模块,如https,fs,这种模块能够帮助咱们系统级的操做API,,使用了事件驱动(回调),非阻塞i/o(异步)的模型,包管理器npmjavascript

进程和线程

  • 进程是操做系统分配资源和调度任务的基本单位php

  • 线程是创建在进程上的一次运行单位,一个进程上能够有多个线程css

  • 咱们常常用的浏览器就是多进程的html

  • 浏览器由用户界面 -> 浏览器引擎(在用户界面和呈现引擎之间传递信息,是浏览器的主进程) -> 渲染引擎前端

  1. (被称为浏览器的内核,浏览器渲染进程)
  2. (一个插件对应一个进程)
  3. (networking网络请求,javascript interpreter js解释器,ui backend ,ui线程,Data Presistence数据存储)
  • 通常咱们先渲染css js和UI是互斥的,执行js空闲下来了就会执行cssvue

  • js是单线程的,不能同时操做domjava

  • 其余线程node

  1. 浏览器事件触发线程(控制事件循环,存放setTimeout,浏览器事件,ajax回调)
  2. 定时触发器线程
  3. 异步HTTP请求线程
  • 单线程特色是节约内存,而且不须要切换执行上下文,不须要管理锁的问题webpack

  • 浏览器中的 Event loopes6

  • webPack多线程 happyPack

webworker 能够配置子线程,,实现多线程

worker.html

console.log(1)
let worker = new Worker('./worker.js')  ;  //h5内置
worker.postMessage(1000);//发消息
worker.onmessage = function(e){//收消息
    console.log(e.data);
};
console.log(2)  
复制代码

worker.js

onmessage = function (e) {   //收消息
    let sum = 200;
    this.postMessage(sum)
}
//不能操做dom
复制代码

关于浏览器event loop

咱们知道js是单线程的,在stack栈里执行,浏览器能够调一些方法,,,如setTimeout属于另外一个线程里的了,会先进行同步代码,将另外一个进程放到一个callback que里面去

  • 队列和栈
  1. 队列:先进先出
  2. 栈:先进后出
console.log()
setTimeout(function(){
    console.log(1)
},1000)
 setTimeout(function(){
    console.log(1)
},5000)
复制代码

若是调用异步代码,不会立刻放到那个到队列里去,栈中的代码执行完以后,回去callback queue(队列中的代码) 里取 ,若是此时已经到1秒,就将函数去除,放到栈里去执行,好比ajax 在成功的时候会放到队列中,造成事件环循环,,event loop死循环 若是事件到达的时候栈中的代码没有执行完,就不会执行队列中的内容

node事件环

node 里面有个应用,他会请求V8引擎 请求后回调用node API 调用完以后会跑到 LIBUV 库, 阻塞 多线程 实现异步 当事情处理完以后经过i/o机制calback,,而后将事件放到队列里去,而后经过node返回给应用

宏任务 和 微任务 -- 异步 执行事件不同,不肯定谁先执行

  • 浏览器 普通代码就是普通的调用栈
  1. 常见的宏任务 setTimeout setIMMediate(只兼容ie) messagechannel(兼容到IE11)
  2. 微任务 vue里的 promise.then MutationObserver是微任务
let channel = new MessageChannel();
let port1 = channel.port1;
let port2 = channel.port2;
port1.postMEessage('hello')
异步代码  vue 规定就是宏任务
port2.onMessage = function(e){
    console.log(e.data)
}
复制代码

浏览器事件环

setTimeout(()=>{
    Promise.resolve('123').then(data =>{
        conssole.log(3)
    })
})
setTimeout(()=>{
        conssole.log(3)
})

//先会执行栈中的内容,再去执行微任务,微任务清空后在执行宏任务,而后循环,宏任务会在栈中执行
复制代码

vue 前身nextick怎么实现的 (兼容性有问题已经被废掉了)

let observe = new MutationOberver(function(){
    console.log('dom全塞进去了')
})
//微任务
observe.observe(div,{child:true}
复制代码

node 环

  • 同步异步只带的是被调用方法
  • 阻塞和非阻塞 是调用者
  • 拿读取文件举例子
  1. 阻塞/非阻塞 同步/异步
  2. 调用者(请求等待)| 请求文件 | 被调用者(赞成) => 同步阻塞
  3. 调用者(请求等待)| 请求文件 | 被调用者(异步回调) => 异步阻塞
  4. 调用者(请求其余)| 请求文件 | 被调用者(异步回调) => 异步非阻塞
  5. 调用者(请求其余)| 请求文件 | 被调用者(赞成) => 同步非阻塞
  • node 属于 异步非阻塞 适用场景大量兵法的输入输出,读取数据,并不复杂处理,聊天,电商网站作接口中间层接口

node运行

vscode 安装插件 code runner 或者爬虫,, 默认状况只认js

  • 所谓的repl就是能和咱们命令窗口交互的 read eval print loop

经常使用命令

  • .help
  • .break Sometimes you get stuck, this gets you out
  • .clear Alias for .break
  • .editor Enter editor mode
  • .exit Exit the repl
  • .help Print this help message
  • .load Load JS from a file into the REPL session
  • .save Save all evaluated commands in this REPL session to a file

global(全局)

  • process 进程
  • argv 执行时的参数 后面写脚手架会用到 相似于 webpack --config --prot --line等 参数解析靠argv
console.log(process.argv)
[ '/usr/local/bin/node',//node的exe文件目录
  '/Users/myloveyunyun/Desktop/node/node.js' ]//执行的文件
复制代码

在cmd执行

取参数

咱们想拿到参数,

console.log(process.argv)
let args ={};
process.argv.slice(2).forEach((item,index)=>{
    if(item.includes('--')){
        args[item] = process.argv.slice(2)[index+1]
    }
})
console.log(args)
复制代码
  • env 环境变量,在开发的时候可能常常输出不少错误,开发可能8080,上线是域名,,上线是不会输出错误的,,咱们通常用它判断是开发环境仍是线上环境
let url;
//怎么配置process.env.NODE_ENV
//mac export 设置环境变量 windows set 能够根据环境变量打出对应的url
if(process.env.NODE_ENV== 'deveopment'){
  url = "http://localhost:3000/api"
}else{
  url = "http://baidu.com"
}
console.log(url)
复制代码

  • pid 一个进程对应的进程号, 手动找到pid 杀进程
  • chdir change directory 改变文件夹 http-server 想读取当前目录内容,须要用chdir 和 cwd
  • cwd current working derectory 读取当前文件夹中的内容
console.log(process.cwd())
let fs = require('fs')
console.log(fs.readFileSync('./1.txt','utf8'))

[Running] node "/Users/myloveyunyun/Desktop/node/2018/node.js"
/Users/myloveyunyun/Desktop/node` `/Users/myloveyunyun/Desktop/node/2018
Error: ENOENT: no such file or directory, open './1.txt'
//这是由于当前读取的是根目录,咱们须要对目录做出拼接
复制代码

咱们能够

process.chdir('./2018')
console.log(process.cwd())
let fs = require('fs')
console.log(fs.readFileSync('./1.txt','utf8'))
复制代码
  • hrtime 已经被取代
  • stdout 标准输出 stderr 错误输出 监听stdin 标准输入 和console.log 一块儿的
process.stdin.on('data',function(data){
    console.log(data)
})
process.stdin.on('data',function(data){
    process.stdout.write(data)
})
111
<Buffer 31 31 31 0a>
复制代码
  • exit 退出进程 主动
  • kill 杀进程 不动
  • buffer 操做二进制 缓存,把数据读到内存中
  • clearImmediate
  • setImmediate 宏任务

这个方法是异步的 process.nextTick 微任务,比宏任务快 和promise.then 比也快 两个都是微任务,这里额nextTick和vue里面的nextTick不是一个东西,一个前端,一个服务端 浏览器不存在此方法 setTimeout 和 setImmediate 顺序是不固定的

setTimeout(function(){
    console.log('setTimeout')
},0)
setImmediate(function(){
    console.log('setImmediate')
},0)

[Done] exited with code=0 in 0.083 seconds

[Running] node "/Users/myloveyunyun/Desktop/node/201803/node.js"
setImmediate
setTimeout

[Done] exited with code=0 in 0.066 seconds

[Running] node "/Users/myloveyunyun/Desktop/node/201803/node.js"
setTimeout
setImmediate
复制代码

Node事件环是怎么运行的

每一个步骤里面都有一个队列

  1. timer(setTimeout,setInterval)
  2. I/0 (不是文件读取 是流,tcp 错误)
  3. idle,prepare(NODE 自带)
  4. poll(轮询, 读文件回调 i/0队列,,还有个功能看timer是否到时间)
  5. check setImemediate
  6. close 关闭 sockect.on('close')

上面的代码咱们只看1/4/5 先走1,可是全部代码都是异步的,node执行的时候,有准备的时间

setTimeout(()=>{
    console.log('setTimeout1')
    Promise.resolve('p').then(()=>{console.log('p')})
},0)
setTimeout(()=>{
    console.log('setTimeout2')
},0)
//在浏览器里
setTimeout1
p
setTimeout2
//node里

[Running] node "/Users/myloveyunyun/Desktop/node/201803/node.js"
setTimeout1
setTimeout2
p

setTimeout(()=>{
    console.log('setTimeout1')
},0)
setTimeout(()=>{
    console.log('setTimeout2')
},0)
//从当前栈切换到队列的瞬间执行微任务
Promise.resolve('p').then(()=>{console.log('p')})

[Running] node "/Users/myloveyunyun/Desktop/node/201803/node.js"
p
setTimeout1
setTimeout2

setImmediate(()=>{
    console.log('setImmediate1');
    setTimeout(()=>{
        console.log('setTimeout1')
    },0)
})

setTimeout(()=>{
    console.log('setTimeout2');
    setImmediate(()=>{
        console.log('setImmediate2');
    })
},0)
//三种状况
//setTimeout2,setImmediate1,setImmediate2,setTimeout1
//setImmediate1,setTimeout2,setTimeout1,setImmediate2
//setImmediate1,setTimeout2,setImmediate2,setTimeout1
//nextTick 插孔执行,在setTimeout,setImmediate切换的时候执行,不是按方法,而是按队列区分.且里面不能写递归,容易死循环,让特定值在下一个队列执行,好处是优先级高于setTimeout
fs.readFile('./1.txt','utf8',()=>{
    setImmediate(()=>{
        console.log('setImmediate1');
    })
    
    setTimeout(()=>{
        console.log('setTimeout2');
    },0)
})
//由于fs在轮询里,setImmediate1,setTimeout2

复制代码
  • setInterval
  • setTimeout
  • 新增
    • console.log('log') //标准输出 process.stdout 默认1
    • console.info('info')//标准输出 process.stdout 默认1
    • console.error('error') //错误输出 默认2
    • console.warn('warn') //错误输出 默认2
    • console.time() - console.timeEnd // 算服务器启动时间
    • console.assert() //测试用 node 自带一个assert 的模块
    • onsole.dir(Array.prototype,{showHidden:true})
  • stream 是一个模块
  • args库

调试 inspect + n

  • 命令行调试 node inspect n下一步, s进, o出, watch('a')监控,watchers
  • node --inspect-brk 文件名 谷歌打开 chrome://inspect 进入调试
  • vscode

模块 seajs * requirejs

CMD 就近依赖, AMD依赖前置 eval 闭包 有关模块化的实现请参考js模块化

咱们重点说CommonJS

  1. 每一个文件都是一个模块
  2. 每一个模块外面都套了个函数,实现模块化功能,闭包
(function(exports,require,moudle,_dirname,_filename){
    
})()
复制代码
  1. 更方便的管理代码

node 模块分类

  1. 内置模块 (核心模块)(最快)fs readFile path http
  2. 文件模块 (本身写的)
  3. 第三方模块
    1. package.json 若是文件夹下没有index文件,就去这个文件查找
    2. 安装包以前要先 npm init -y 初始化
    3. 安装包 npm install name
    4. 会去node_modules文件夹查找
    5. 找不到会去查找上级目录
  • fs模块
fs.accessSync('1.txt')//判断文件是否存在
复制代码
  • path模块 //resolve join basename extname
//resolve // 解析绝对路径,传多个参数能够拼接
//join // 解析相对路径,传多个参数能够拼接
let path = require('path');
console.log(path.resolve('./2.txt','a','b'))
console.log(path.join(__dirname,'./2.txt','a','b'))
console.log(__dirname)
console.log(__filename)

[Running] node "/Users/myloveyunyun/Desktop/node/201803/node.js"
/Users/myloveyunyun/Desktop/node/2.txt/a/b
/Users/myloveyunyun/Desktop/node/201803/2.txt/a/b
/Users/myloveyunyun/Desktop/node/201803
/Users/myloveyunyun/Desktop/node/201803/node.js


console.log(path.extname('1.a.d.f')) // 后最
console.log(path.basename('1.a.s.f.g','.g')) //基础名字
console.log(path.sep)//环境变量分隔符
console.log(path.posix.sep)//mac下分隔符 /windows \
console.log(path.delimiter)//mac下; /windows:

[Running] node "/Users/myloveyunyun/Desktop/node/201803/node.js"
.f
1.a.s.f
/
;

复制代码
  • vm模块 虚拟机
let vm = require('vm');
let a = 'sd'
vm.runInThisContext('console.log(a)') //沙箱
eval('console.log(a)'),会查找

ReferenceError: a is not defined
    at evalmachine.<anonymous>:1:1
复制代码
  • vm模块 虚拟机
let vm = require('vm');
let a = 'sd'
vm.runInThisContext('console.log(a)') //沙箱
eval('console.log(a)')//会查找

ReferenceError: a is not defined
    at evalmachine.<anonymous>:1:1
复制代码

Conmon.js 实现

ConmonJS规范

  • 定义了如何导入模块 require
  • 还定义了如何导出模块 module.exports 导出xxx
  • 还定义了一个js就是一个模块
  • 若是第一次加载完成,则会存到缓存里,第二次从缓存中读取

下面咱们根据以上三个特色,考虑各类状况,一步步实现一个简单的CommenJS引入功能

  1. 首先咱们要引入所用到的模块

    a. 读文件

    b. 获取文件路径

    c. 运行环境

    d. 加载策略=>针对js/json/node文件

    e. Module

    f. 缓存

    g. requier方法

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._extensions = {
    //js优先级高于json,和node 
    '.js': function (Module) {},
    '.json': function (Module) {},
    '.node': 'xxx'
}
Module._cacheModule = {}// 根据的是绝对路径进行缓存的 
function require(moduleId){//咱们将加载模块以参数形式传递过来
    
}
复制代码

以上是咱们读取模块必备的条件,下面咱们挨个增长内容, 2. 在require接收到路径的时候,咱们首先要对此路径作解析,假设咱们给个方法_resolveFileName(moduleId)对路径做出解析

// 解析绝对路径的方法 返回一个绝对路径
Module._resolveFileName = function (moduleId) {
  let p = path.resolve(moduleId);
  // 没有后缀在加上后缀 若是传过来的有后缀就不用加了
  if (!path.extname(moduleId)) {//extname是path内部方法,在这里用到的相似用法,清自行查阅,笔者就先很少作解释了
    //keys将一个对象转成数组
    let arr = Object.keys(Module._extensions);
    //若是没有后蕞名称,由于只有三种状况,咱们挨个对比,在这里是有前后顺序的,假设传过来a,咱们会现识别a.js
    for (let i = 0; i < arr.length; i++) {
      let file = p + arr[i];
      try {
        fs.accessSync(file);//accessSync 同步断定分拣是否存在
        return file;
      } catch (e) {
        console.log(e)
      }
    }
  } else {
    return p;//若是有后坠就直接查找此文件,无需匹配
  }
}
function req(moduleId) {
  let p = Module._resolveFileName(moduleId);// p是一个绝对路径
}
复制代码
  1. 当咱们解析完绝对路径以后,就须要去查找要加载的文件了,咱们以前说若是是第二次加载,就从缓存中查找,因此这里咱们首先须要判断有没有缓存
Module._extensions = {
    //js优先级高于json,和node 
    '.js': function (Module) {},//这里的function指的是加载该类型文件的方法
    '.json': function (Module) {},
    '.node': 'xxx'
}
function req(moduleId) {
  let p = Module._resolveFileName(moduleId);// p是一个绝对路径
  if (Module._cacheModule[p]) {
    // 模块存在,若是有直接把对象返回便可稍后补充
  }
  // 表示没有缓存就生成一个模块
  let module = new Module(p);
  // 加载模块
  let content = module.load(p); // 加载模块
  Module._cacheModule[p] = module;
  module.exports = content; //最终以module.export导出
  return module.exports
}
复制代码

在此过程当中 ,咱们生成一个模块,new了一个moudle, 将路径传过去,还记得上面的代码,在new的过程当中,咱们给模块加了id,exports,load,而后我加载此模块,而且将它添加到缓存中 load方法是在实例上调用的,咱们将吧这个方法写在Module的原型上

//在new以后就有了这些标识
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); //成功读取文件内容
    //this指当前模块的实例 有id exports loaded
    return content;
}
复制代码
  1. 最后咱们来补充下加载文件的方法,这里只介绍json和js的 若是是json咱们直接parse,若是是js,咱们说一个js是一个模块,那说明每读取一个js至关于读取一个闭包文件,咱们会在js文件内容外包一个闭包,而后导出用moudul.export
//exports,require,module
Module.warpper = ['(function(exports,require,module){', '\n})'];
Module._extensions = {
    //js优先级高于json,和node 
    '.js': function (Module) {
        let script = fs.readFileSync(module.id, 'utf8');
        let fn = Module.warpper[0] + script + Module.warpper[1];
        //咱们须要执行此文件,可是eval会在当前做用域向上查找,咱们只想在require以后执行此文件,所以这里使用沙漏限制环境
        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': 'xxx'
}
复制代码

这样咱们的基本功能就实现了

// 什么是commonjs规范
// 定义了如何导入模块 require
// 还定义了如何导出模块 module.exports 导出xxx
// 还定义了一个js就是一个模块
let fs = require('fs');
let path = require('path');
let vm = require('vm');
//解析绝对路径方法,返回绝对路径
//因为es6不支持静态属性,咱们暂时用es5实现
//全部加载策略
function Module(p) {
    this.id = p; // 当前模块的标识
    this.exports = {}; // 每一个模块都有一个exports属性
    this.loaded = false; // 这个模块默认没有加载完
}
// 全部的加载策略
Module.warpper = ['(function(exports,require,module){', '\n})'];
Module._extensions = {
    //js优先级高于json,和node 
    '.js': function (Module) {
        let script = fs.readFileSync(module.id, 'utf8');
        let fn = Module.warpper[0] + script + Module.warpper[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': 'xxx'
}
//根据绝对路径进行缓存
Module._cacheModule = {};
Module.resolveFileName = function (ModuleId) {
    let p = path.join(ModuleId);
    //若是后最不存在则查找
    if (!path.extname(ModuleId)) {
        //keys将一个对象转成数组
        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;
    }
}
Module.prototype.load = function (filepath) {
    //判断加载的文件是json仍是node,仍是js
    let ext = path.extname(filepath)
    let content = Module._extensions[ext](this); //成功读取文件内容
    //this指当前模块的实例 有id exports loaded
    return content
}

function req(ModuleId) {
    //解析绝对路径,
    //且判断文件类型json,js,node
    //得倒真实路径去查找缓存
    let p = Module.resolveFileName(ModuleId); //p是一个绝对路径
    if (Module._cacheModule[p]) {
        // 模块不存在,若是有直接把exports对象返回便可
        return Module._cacheModule[p].exports;
    }
    //没有缓存则建立一个模块
    let moudule = new Module(p);
    let content = module.load(p);
    Module._cacheModule[p] = module;
    module.exports = content;
    return module.exports;
    //加载模块

}
let a = req('./a.js');
req('./a.js');
console.log(a);
复制代码

对于文件模块,若是js,json,node都不存在其实会找文件夹下的package.json,若是json文件没有,会找该文件夹下的index,在这里咱们不作过多的阐述。 文件模块查找规则

相关文章
相关标签/搜索