Node基礎之總體概覽

Diasa發表於2018-06-25

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)
  • 棧中存放基本資料型別,堆中存放引用資料型別。
  • 瀏覽器事件環

Node基礎之總體概覽

同步和非同步 阻塞和非阻塞

  • 同步和非同步指代的是被呼叫而言的
  • 阻塞和非阻塞針對的是呼叫者而言的

Node基礎之總體概覽

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基礎之總體概覽

    // 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
複製程式碼

相關文章