Node是什麼
Node.js
是一個基於Chrome V8
引擎的JavaScript執行環境(runtime)
,Node不是一門語言是讓js執行在後端的執行時,並且不包括javascript全集,因為在服務端中不包含DOM
和BOM
,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
複製程式碼