Node 基礎學習

node是什麼

  • Node.js 是一個基於 Chrome V8 引擎的 JavaScript 運行環境。
  • Node.js 使用了一個事件驅動、非阻塞式 I/O 的模型。
  • Node使用包管理器NPM。

第一句話

Node.js 是一個基於 Chrome V8 引擎的 JavaScript 運行環境。

運行環境, 不是一門語言,不是一個框架。只是可以做爲JavaScript代碼運行的一個環境。 javascript

而這個運行環境主要是由V8提供的。html

V8作了什麼?

建立了一個callstack。vue

function main(){
    func1();
}
function func1(){
    func2();
}
function func2(){
    console.log(1);
}
main();

V8 Engine

除去V8,Node中還有哪些東西?

除去V8,Node中另一個比較重要的組成就是 libuvjava

What? libuv是什麼鬼?node

先說說,關於Node的另一句話:webpack

Node is designed to build scalable network applications.

這句話的底氣在哪兒,就是Node自己採用的 事件驅動,非阻塞I/O模型c++

併發模型構建網絡的應用中,每一個鏈接都會生成一個新線程,每一個新線程可能須要 2MB 的配套內存。在一個擁有 8 GB RAM 的系統上,理論上最大的併發鏈接數量是 4,000 個用戶。隨着您的客戶羣的增加,若是但願您的 Web 應用程序支持更多用戶,那麼,您必須添加更多服務器。因此在傳統的後臺開發中,整個 Web 應用程序架構(包括流量、處理器速度和內存速度)中的瓶頸是:服務器可以處理的併發鏈接的最大數量。這個不一樣的架構承載的併發數量是不一致的。並且,多個線程存在的話,也會衍生出線程切換的問題,這也會佔用必定資源。 git

併發存在的兩個問題:github

  • Execution stacks take up memory: 資源有限
  • Context switching is not free:佔用額外資源

traditional-web-server-model

Node的解決這個問題思路:web

  1. 在網絡應用中,比較慢的環節主要在 磁盤讀取 或 網絡請求階段,這時候CPU處於閒置狀態。
  2. 若是在等待I/O操做時,可以釋放處於閒置狀態的CPU,則能夠很大程度上利用資源;
  3. 那麼接下來的問題就在於,如何在I/O 操做完成後,繼續執行後續的操做;
  4. 解決方案:事件驅動,採用回調。這和瀏覽器中的Event Loop是同樣的道理。

市場上已經有一些使用一樣處理思路的框架。

  • EventMachine:處理網絡請求的框架,主要是面向ruby;
  • Twisted:用Python實現的基於事件驅動的網絡引擎框架;

nodejs-process-model

具體是怎麼作的?

在程序運行時,V8 會把I/O操做等耗時較多操做及相關回調一併交給libuv去處理。而V8繼續執行後面的代碼。等到I/O操做完成後,libuv會將回調方法放到事件隊列中。

const fs = require('fs');

const readFile = (file) => {
    fs.readFile(file, (err, data) => {
        if(!err) console.log(data);
    });
}

console.log('program start ......');
readFile('file.json');
console.log('readFile has put the I/O ');
console.log('program end!!!!!');

V8 async

負責異步程序調度的工做就是libuv作的事情。

Libuv is a multi-platform support library with a focus on asynchronous I/O.

在上述程序中,當遇到文件讀取操做時,V8會把js接口轉成C++接口 同時把回調方法一併交給libvu。此時libuv接管這個文件讀取任務。當文件讀取完成後,libuv就會把這個事件的回調函數扔到事件隊列裏。當下一次檢查事件隊列時,就會執行該回調函數。

question: 既然這樣,怎麼理解node中的單線程?

再捋一捋Node, V8 和libuv的關係。

1) Node主要由V8 javascript引擎和libuv組成;
2) v8引擎主要負責解釋執行js代碼,碰到須要異步的操做會交給libuv處理;
3) libuv自己是獨立的c語言庫,能夠直接使用c/c++來調用;

在瀏覽器端,JS是沒有能力進行文件讀取操做的。js最初的能力也就限制在表單校驗上。而在Node中,JS能夠操做本地文件,創建網絡鏈接。這確定是Node乾的好事!

再來講說Node中其它一些組成部分

Node擴展了JS的能力:builtin modules

builtin modules是由C++代碼寫成各種模塊,包含了crypto,zlib, file, net等基礎功能。

native modules

除了builtin modules, 還有一個native modules。它們是用js編寫的內建模塊,提供給程序開發者使用。

  • fs
  • http

builtin modules 和 native modules都屬於核心模塊。核心模塊在Node源碼編譯的過程當中,編譯進了二進制文件。因此在Node進程啓動的時候,部分核心模塊就已經被直接加載到了內存當中。

至此,Node的基本構成和運行原理已經講完了。

Node內部結構
Node 構成

補充一句:Node.js的單線程並非真正的單線程,只是開啓了單個線程(能夠定義爲主線程)進行業務處理,同時開啓了其餘線程專門處理I/O。當一個指令到達主線程,主線程發現有I/O以後,直接把這個事件傳給libuv處理。libuv會管理一個線程池,I/O操做就是由線程池裏面的線程完成的。等I/O 操做完成,libuv,會把對應I/O操做的回調放到事件循環當中。在線程上,不會等待I/O 操做完成,繼續執行後續的代碼。這就是「單線程」、「異步I/O」。

IO.js

var fork = require('child_process').fork;
var fs = require('fs');

console.log('start......');

// blocking
// console.log(fs.readFileSync('test.json', 'utf-8'));

// non blocking
var childProcess = fork('another-thread.js');
childProcess.on('message', function(data){
    console.log(data);
})

console.log('end !!!');

another-child.js

var fs = require('fs');
process.send( fs.readFileSync('test.json', 'utf-8'));
Everything runs in parallel except your code! 在Node中)除了代碼,一切都是並行的!

因爲node中主任務的執行是以單線程的方式進行,若是程序出錯致使崩潰,就會終止整個流程。爲此,市場上有些Node進程管理工具,它們會維護Node程序的狀態,當程序掛掉時,會自動重啓。好比咱們使用的pm2

NPM

對於node沒有的一些模塊(native modules),能夠引入外部模塊。這些外部模塊一般是其它開發者貢獻的。

那麼問題來了,對於數量衆多的模塊中,如何快速找到本身想要的並可以快速的引進到本身的項目當中。

這就是npm幫咱們作的工做。

Use npm to install, share, and distribute code; manage dependencies in your projects; and share & receive feedback with others.

npm官網

  • 模塊安裝:
  • 模塊共享
  • 發佈代碼
  • 管理依賴
  • 共享和反饋

NPM的基本模式

NPM是JavaScript包的管理器。

NPM basic

廣義的npm的構成

npm consists of three distinct components:

  • the website: NPM官方站點
  • the Command Line Interface (CLI) : NPM命令行工具
  • the registry: JS模塊的數據庫

Use the website to discover packages, set up profiles, and manage other aspects of your npm experience. For example, you can set up Orgs (organizations) to manage access to public or private packages.

The CLI runs from a terminal. This is how most developers interact with npm.

The registry is a large public database of JavaScript software and the meta-information surrounding it.

npm中的使用

npm默認隨node一塊兒安裝,在Node安裝完成後,npm已經安裝。

查找一個包

去npm官網,按關鍵詞查找。

管理模塊

  • npm install [module name] :普通安裝方式,包安裝完成後,會在當前目錄生成一個node_modules目錄。這是一個存放外部js模塊的地方,經過npm安裝的包都放在node_modules下。
  • npm install -g [module name]:全局安裝,模塊被安裝在node安裝路徑下的 node_modules中。
  • npm install [folder path]:能夠指定npm 安裝某個目錄folder path下的的文件,前提是這個目錄下包含package.json文件。
  • npm install [module name]@[version]: 安裝包的時候,指定對應的版本號。

    • npm isntall chalk@latest : 安裝最新版
    • npm install chalk@2.0.0: 安裝2.0.0版
    • npm install chalk@">=2.0.0":安裝大於2.0.0的版本
  • npm install --save-prod [module name]: 在本地安裝包,並將安裝信息寫入 package.json文件中的dependencies中, 不寫--save-prod 或者只寫 --save 默認跟 --save-prod同樣。
  • npm install --save-dev [module name]:在本地安裝包,並將安裝信息寫入 package.json文件中的devDependencies

    • dependencies:在生產環境中須要用到的依賴
    • devDependencies:在開發、測試環境中用到的依賴
  • npm update [module name]: 更新本地模塊
  • npm uninstall [module name]: 卸載模塊

package.json

package.json是一個node和npm都會自動讀取的配置文件,它裏面是個標準的JSON格式字符串。

對於NPM而言, package.json作了如下工做:

  • 存儲項目依賴的全部包
  • 容許你指定項目依賴的包的版本規則,不知足項目需求的版本,不須要
  • 讓你的項目構建具備可複用性,容易分享你的項目

對於你的項目而言,package.json定義了一些基礎信息,好比項目名稱,版本等等。

pakcage.json必須具備的兩個字段: nameversion。這倆個字段有什麼意義呢?
NPM 做爲一個包管理平臺,當有開發者提交(publish)模塊時,必須提供一些基本信息便於管理。

  • name: 項目名稱或者模塊名稱
  • version:版本號,應當遵循 x.x.x的格式
  • description:項目信息描述,方便別人瞭解你的模塊,也利於搜索
  • keywords:項目的關鍵詞,便於搜索
  • homepage:項目的主頁;
  • scripts:scripts屬性是一個對象,裏面的每個屬性對應一段腳本;腳本可使用 npm run + 屬性名 執行
  • main:指定項目的程序入口文件,該文件的exports對象同時也是require項目時,取到的對象。
  • repository:指明代碼存在的地址,便於別人更好的查看你的源碼
  • dependencies:一個對象,配置模塊依賴的模塊列表,key是模塊名稱,value是版本描述(遵循semantic規則)
  • devDependencies:一個對象,開發或測試過程當中的一些依賴模塊,跟上線後的依賴模塊區分出來
  • engines: 指定項目運行的Node版本
  • author:項目的開發者
  • contributors:一堆項目的開發者
{
  "name": "vue-todo",
  "version": "1.0.0",
  "description": "a simply todolist using vuejs",
  "scripts": {
    "start": "node server.js",
    "stop": "egg-scripts stop --title=egg-server-example",
    "dev": "egg-bin dev"
  },
  "dependencies": { // 線上生產環境必須,固然開發環境也會用到
    "babel-runtime": "^6.23.0",
    "vue": "^2.0.1",
    "vue-localstorage": "^0.1.1",
    "vuex": "^2.2.1"
  },
  "devDependencies": { // 開發環境會用到的東東
    "webpack": "^1.13.2",
    "webpack-dev-middleware": "^1.8.3",
    "webpack-hot-middleware": "^2.12.2",
    "webpack-merge": "^0.14.1"
  }
}

semantic versioning(語義化版本規則)

版本格式:主版本號.次版本號.修訂號, 例如 1.2.3

版本號遞增規則以下:

  • 主版本號:當你作了不兼容的 API 修改; 1.2.3 ---> 2.0.0
  • 次版本號:當你作了向下兼容的功能性新增; 1.2.3 ---> 1.3.0
  • 修訂號:當你作了向下兼容的問題修正; 1.2.3 ---> 1.2.4

在package.json定義版本規則的時候,能夠這麼作:

  • 若是隻打算接受補丁版本的更新(也就是最後一位的改變),就能夠這麼寫:

    • 1.0
    • 1.0.x
    • ~1.0.4
  • 若是接受小版本的更新(第二位的改變),就能夠這麼寫:

    • 1
    • 1.x
    • ^1.0.4
  • 若是能夠接受大版本的更新(天然接受小版本和補丁版本的改變),就能夠這麼寫:

    • *
    • x

在使用npm install --save || npm install --save-dev 安裝的時候,寫入pakcage.json中的依賴,默認接受小版本的更新,即在版本號前添加 '^'。

NPM document

Node 模塊

Node中的模塊分爲兩類:

  • 核心模塊(Node中內嵌的,好比fshttp等)
  • 文件模塊(開發者本身編寫的,NPM上的模塊都屬於開發者自定義的模塊)

文件模塊是在運行的時候,動態加載。須要進行路徑分析,文件定位 和 編譯執行。

Node加載模塊時,優先從緩存中加載,若是緩存中不存在該模塊,纔會按照上述的三個步驟進行模塊加載。

模塊標識符在Node中,主要有如下幾類:

  • 核心模塊,好比fs, http
  • ...開頭的相對路徑文件模塊
  • / 開頭的絕對路徑文件模塊
  • 非路徑形式的文件模塊;

這幾類模塊的加載速度是依次下降的。

module.js

同瀏覽器中的window同樣,在Node中的全局變量都掛在global下。
先說一下,經常使用到一些變量:

  • __dirname: 當前模塊所在目錄
  • __filename: 當前模塊文件名稱
  • require: 引入其它模塊
  • module:
  • exports

上面5個變量,貌似全局變量,但不是全局變量。他們都是模塊系統下的東西。

question: 它們不在全局變量下,那它們爲什麼能夠在模塊中直接調用?

Node引入模塊

在Node中,引入模塊分爲三個步驟:

  • 路徑分析
  • 文件定位
  • 編譯執行

模塊編譯

編譯和執行是引入文件模塊的最後一個階段。

  • .js文件 : Node 會對js源碼進行一個首尾的封裝。返回一個function,並將當前的環境的exports,require,module,__dirname,__filename做爲形參傳遞給這個function。

包裝後的代碼:

(function(exports, require, module, __filename, __dirname){
    // js文件中的源碼
})

這就是爲何 它們不在 全局變量下,卻能夠在模塊當中使用的緣由。

  • .node文件: 對於.node文件, 實際上並不須要編譯過程。由於.node文件自己就是C/C++編譯後的文件,它只有加載和執行過程。
  • .json文件: Node 會讀取json文件內容,並將它賦予exports對象,直接傳遞給第三方調用。

Node_module_callstack

在NPM中的模塊,基本屬於Node中文件模塊裏的Javascript模塊。

require的規則

Node的模塊系統參照CommonJS規範實現。

  1. 若是參數字符串不以.//../開頭,說明要加載的不是一個文件,而是一個默認提供的核心模塊。

    1. 此時則先在node平臺所提供的核心模塊當中找;
    2. 而後再尋找NPM模塊(即第三方模塊包,或本身寫的模塊包)

      1. 在尋找NPM模塊包時,會從當前目錄出發,向上搜索各級當中的node_modules文件夾當中的文件,但如有兩個同名文件,則遵循就近原則。(module.paths是一個模塊路徑數組。)
  2. 若是require當中的參數字符串以/開頭:則表示從系統根目錄開始尋找該模塊文件。
  3. 若是require當中的參數字符串以./(從當前目錄出發)或../(從上一級目錄出發)開頭:表示按照相對路徑,從當前文件所在的文件夾開始尋找要載入的模塊文件。

    1. 按js文件來執行(先找對應路徑當中的module.js文件來加載)
    2. 按json文件來解析(若上面的js文件找不到時,則找對應路徑當中的module.json文件來加載)
    3. 按照預編譯好的c++模塊來執行(尋找對應路徑當中的module.node文件來加載)
    4. 若參數字符串爲一個目錄(文件夾)的路徑,則自動先查找該文件夾下的package.json文件,而後再再加載該文件當中main字段所指定的入口文件。(若package.json文件當中沒有main字段,或者根本沒有package.json文件,則再默認查找該文件夾下的index.js文件做爲模塊來載入。)

Node查找模塊示意圖

process

process 對象是一個全局變量,它提供當前 Node.js 進程的有關信息,以及控制當前 Node.js 進程。

  • process.argv: 包含命令行參數的數組。第一個元素會是'node',第二個元素將是.js文件的名稱,接下來的參數依次是命令行參數
  • process.execArgv: 啓動進程所需的 node 命令行參數。這些參數不會在 process.argv 裏出現,而且不包含 node 執行文件的名字,或者任何在名字以後的參數。這些用來生成子進程,使之擁有和父進程有相同的參數
  • process.env: 獲取當前系統環境信息的對象,輸出內容是環境變量等內容,這個對象能夠修改
  • nextTick(callback): 將callback放到事件輪詢隊列首位,下一次事件輪詢開始時,先執行callback
  • process.abort:結束當前進程
  • process.kill(pid):結束一個進程

process.js

console.log(process.argv);
console.log(process.execArgv);
node process.js
# [ '/usr/local/bin/node', '/root/node-demo/process.js' ]
# []

node process.js abc 234 cvb=cvb
# [ '/usr/local/bin/node',
#   '/root/node-demo/process.js',
#   'abc',
#   '234',
#   'cvb=cvb' ]
# []


node --harmony  --use-openssl-ca  process.js abc 234 cvb=cvb
# [ '/usr/local/bin/node',
#   '/root/node-demo/process.js',
#   'abc',
#   '234',
#   'cvb=cvb' ]
# [ '--harmony', '--use-openssl-ca' ]

Node'Process document

fs

file.js

Node'FileSystem document

questions require('../fs/file.js')這裏是異步仍是同步?

deno,下一代Node?

  • package.json
  • node_modules
  • gyp

等等............

the heaviest object

Node之父JS大會介紹deno時的PPT(2018)

Node之父JS大會介紹Node時的PPT(2009)

Nodejs的運行原理-科普篇

視頻:callstack 和異步 基本原理

Awesome Micro npm Packages

libuv 官網

[深刻淺出Node.js]()

相關文章
相關標籤/搜索