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,在這裏咱們不作過多的闡述。 文件模塊查找規則

相關文章
相關標籤/搜索