V8最大的特點(diǎn)就是單線程,一次只能運(yùn)行一個(gè)任務(wù)
node存在大量異步操作
在異步操作中侦副,無法通過try...catch...捕獲異常
異步回調(diào)相比傳統(tǒng)代碼:
采用異步事件驅(qū)動(dòng)
不容易閱讀
不容易調(diào)試
不容易維護(hù)
什么是進(jìn)程?
每一個(gè)正在運(yùn)行的程序都可稱之為進(jìn)程
每一個(gè)應(yīng)用程序至少有一個(gè)進(jìn)程
多線程
如果是多線程:
node進(jìn)程啟動(dòng)后會(huì)默認(rèn)創(chuàng)建一個(gè)線程(主線程),用于執(zhí)行代碼没咙, 對(duì)于比較花費(fèi)
時(shí)間的操作,再給它創(chuàng)建一個(gè)線程去執(zhí)行千劈。主線程往 下走祭刚,碰到比較耗時(shí)的操作,再給這端代碼創(chuàng)建一個(gè)線程去執(zhí)行...
那么問題來了墙牌,線程越多越好嗎涡驮?
答案是否定的
----多線程帶來的問題:
--線程之間共享某些數(shù)據(jù),同步某個(gè)狀態(tài)都很麻煩
--創(chuàng)建線程花費(fèi)時(shí)間
--能夠創(chuàng)建的線程數(shù)量有限
--CPU切換線程時(shí)要切換上下文喜滨,非常耗時(shí)
node單線程執(zhí)行過程:
node內(nèi)部有一個(gè)事件隊(duì)列捉捅,當(dāng)碰到耗時(shí)操作比如文件操作,將該事件加入事件隊(duì)列虽风,主線程往下執(zhí)行锯梁,又碰到耗時(shí)操作,再往事件隊(duì)列中加入該事件焰情,以此類推陌凳,
主線程執(zhí)行完下面的代碼(主線程空閑),去執(zhí)行事件隊(duì)列中的代碼操作内舟,執(zhí)行完之后合敦,執(zhí)行它的回調(diào),回調(diào)完成验游,執(zhí)行跟它的回調(diào)相關(guān)的代碼操作(如果有)充岛,如果這個(gè)操作是耗時(shí)的比如文件操作(如果是不耗時(shí)的,即非阻塞的耕蝉,則不用交出去崔梗,直接自己執(zhí)行了就好),將該操作加入事件隊(duì)列垒在,主線程空閑時(shí)蒜魄,執(zhí)行后來的這個(gè)事件隊(duì)列中的事件,執(zhí)行完該事件场躯,執(zhí)行它的回調(diào)...
對(duì)于事件隊(duì)列谈为,不管是主線程開始碰到的事件還是回調(diào)函數(shù)中的事件,都是執(zhí)行到
(碰到)這部分代碼的時(shí)候踢关,將其加入事件隊(duì)列的隊(duì)尾伞鲫,而事件隊(duì)列中的事件的執(zhí)行順序是按照隊(duì)列的順序執(zhí)行的
注意,本質(zhì)上node程序的處理還是多線程的签舞,node主線程負(fù)責(zé)處理源程序事件隊(duì)
列中的非阻塞的操作秕脓,而阻塞的操作柒瓣,則交給線程池中的線程去處理,線程池中的
線程幫助處理完這個(gè)事件吠架,利用回調(diào)函數(shù)將結(jié)果送給主線程芙贫。總之诵肛,node本身主
線程主要做調(diào)度工作(和非阻塞操作)屹培。
線程池中有一些已經(jīng)創(chuàng)建好的線程默穴,供node主線程調(diào)用
非阻塞的好處:
--提高代碼響應(yīng)效率
--充分利用單核CPU的優(yōu)勢(shì)
--改善I/O的不可預(yù)測帶來的問題
但是目前市面上大多是多核CPU怔檩,大多通過硬件虛擬化將多核虛擬成單核
在node中,啟用嚴(yán)格模式‘use strict’
V8對(duì)ES6支持情況分為三種情況:不支持蓄诽、直接支持薛训、嚴(yán)格模式下支持
比如let 就需要啟用嚴(yán)格模式,或者用其他方式做些轉(zhuǎn)換
chrome瀏覽器在打開網(wǎng)頁請(qǐng)求的時(shí)候仑氛,會(huì)自動(dòng)請(qǐng)求項(xiàng)目根目錄下面的favicon.ico
圖標(biāo)
node采用CommonJS規(guī)范乙埃,模塊和文件是一一對(duì)應(yīng)關(guān)系,加載一個(gè)模塊锯岖,實(shí)際上就是加載對(duì)應(yīng)的一個(gè)模塊文件介袜。
CommonJS
CommonJS API定義很多普通應(yīng)用程序(主要指非瀏覽器的應(yīng)用)使用的API,從
而填補(bǔ)了這個(gè)空白出吹。它的終極目標(biāo)是提供一個(gè)類似Python遇伞,Ruby和Java標(biāo) 準(zhǔn)庫。
這樣的話捶牢,開發(fā)者可以使用CommonJS API編寫應(yīng)用程序鸠珠,然后這些應(yīng)用可以運(yùn)行
在不同的JavaScript解釋器和不同的主機(jī)環(huán)境中。
在兼容CommonJS的系統(tǒng)中秋麸,你可以使用 JavaScript程序開發(fā):
--服務(wù)器端JavaScript應(yīng)用程序
--命令行工具
--圖形界面應(yīng)用程序
--混合應(yīng)用程序(如渐排,Titanium或Adobe AIR)
Node.js采用了這個(gè)規(guī)范,根據(jù)CommonJS規(guī)范灸蟆,一個(gè)單獨(dú)的文件就是一個(gè)模塊驯耻。
加載模塊使用require方法,該方法讀取一個(gè)文件并執(zhí)行炒考,最后返回文件內(nèi)部的
exports對(duì)象吓歇。
//sum.js
exports.sum = function(){...做加操作..};
//calculate.js
var math = require('sum');
exports.add = function(n){
return math.sum(val,n);
};
--------------------
ES6中箭頭函數(shù)與普通函數(shù)this的區(qū)別
普通函數(shù)中的this:
- this總是代表它的直接調(diào)用者, 例如 obj.func ,那么func中的this就是obj
2.在默認(rèn)情況(非嚴(yán)格模式下,未使用 'use strict'),沒找到直接調(diào)用者,則this指的是 window
3.在嚴(yán)格模式下,沒有直接調(diào)用者的函數(shù)中的this是 undefined
4.使用call,apply,bind(ES5新增)綁定的,this指的是 綁定的對(duì)象
箭頭函數(shù)中的this
默認(rèn)指向在定義它時(shí),它所處的對(duì)象,而不是執(zhí)行時(shí)的對(duì)象, 定義它的時(shí)候,可能環(huán)境是
window(即繼承父級(jí)的this);
模塊的分類
----文件模塊,就是我們自己寫的功能模塊文件
自定義模塊開發(fā)流程:
創(chuàng)建模塊--------new calc.js(新建一個(gè)calc.js文件)
導(dǎo)出模塊--------module.exports = {}
載入模塊--------const calc = require('./calc.js')
使用模塊--------calc.add(1, 2)
----核心模塊票腰,Node平臺(tái)自帶的一套基本功能模塊城看,也稱為Node平臺(tái)的API
----第三方模塊,社區(qū)或第三方個(gè)人開發(fā)好的功能模塊杏慰,可以直接拿來用
模塊中的全局成員
__dirname 獲取當(dāng)前腳本所在目錄路徑
__filename 獲取當(dāng)前腳本文件所在目錄路徑
console.log(__dirname);
console.log(__filename);
輸出:
F:\fore-end\materials\Node.js\projs\proj2
F:\fore-end\materials\Node.js\projs\proj2\test.js
如果想要在test.js中讀取上一個(gè)目錄中的content.txt文件
const fs = require('fs');
//所有的文件操作路徑都應(yīng)該是絕對(duì)路徑(物理路徑)
fs.readFile(dirname + '/../content.txt', (error, content) => {
if (error) throw error;
console.log(content.toString());
});
每個(gè)模塊內(nèi)部都是私有空間
//test.js
const fs = require('./error.js')
console.log(a);
//error.js
let a;
//執(zhí)行結(jié)果:
a is not defined
模塊內(nèi)部是一個(gè)獨(dú)立的作用域测柠,所以模塊內(nèi)部變量和方法不會(huì)污染全局炼鞠,而在客戶
端通過script標(biāo)簽引入JS文件,那么他們具有相同的作用域
node有一個(gè)module對(duì)象轰胁,我們打印一下這個(gè)對(duì)象
//test.js
const fs = require('./error.js')
module.exports = {
print: () => (console.log(1))
}
console.log(mudole);
//error.js
let a;
//執(zhí)行test.js打印module對(duì)象
Module {
id: '.',
exports: { print: [Function: print] },
parent: null,
filename: 'F:\\fore-end\\materials\\Node.js\\projs\\proj2\\test.js',
loaded: false,
children:
[ Module {
id: 'F:\\fore-end\\materials\\Node.js\\projs\\proj2\\error.js',
exports: {},
parent: [Circular],
filename: 'F:\\fore-end\\materials\\Node.js\\projs\\proj2\\error.js',
loaded: true,
children: [],
paths: [Object] } ],
paths:
[ 'F:\\fore-end\\materials\\Node.js\\projs\\proj2\\node_modules',
'F:\\fore-end\\materials\\Node.js\\projs\\node_modules',
'F:\\fore-end\\materials\\Node.js\\node_modules',
'F:\\fore-end\\materials\\node_modules',
'F:\\fore-end\\node_modules',
'F:\\node_modules' ] }
當(dāng)每個(gè)js文件在執(zhí)行或被require的時(shí)候谒主,NodeJS其實(shí)創(chuàng)建了一個(gè)新的實(shí)例var module = new Module(),這個(gè)實(shí)例名叫module赃阀。
可以發(fā)現(xiàn)module對(duì)象有一個(gè)id屬性霎肯,表示這個(gè)module是哪個(gè)module,點(diǎn).表示當(dāng)前modulo
parent和children屬性用來表示當(dāng)前module的parent和children模塊是哪個(gè)模塊
看到module對(duì)象還封裝了一個(gè)exports對(duì)象榛斯,初始值是個(gè)空對(duì)象观游,module.export
其實(shí)是給Module實(shí)例中的exports對(duì)象中添加方法/屬性。
exports對(duì)象
通常使用exports的時(shí)候驮俗,是這么用的:
exports.print = function(){console.log(12345)}
假設(shè)我有一個(gè)JS文件內(nèi)容如下:
console.log(module); //你會(huì)看到Module中的exports為空對(duì)象{}
console.log(exports); //你會(huì)看到Module中的exports為空對(duì)象{}
module.exports = {
print : function(){console.log(12345)}
}
console.log(module); //你會(huì)看到Module中的exports對(duì)象有了print()方法
exports.name = '小白妹妹';
console.log(module); //你會(huì)看到Module中的exports對(duì)象不僅有了print()方法懂缕,
還有了name屬性
由此也能看出,傳說中的exports其實(shí)是module.exports的引用王凑,你可以這么理解搪柑,NodeJS在你的代碼之前悄悄的加了以下代碼:
var module = new Module();
var exports = module.exports;
exports是module.exports的引用的意思就是兩者指向同一個(gè)對(duì)象,當(dāng)然也可以改變exports的指向索烹,是她不再和module.exports指向同一個(gè)對(duì)象工碾。
改變/設(shè)置對(duì)象的指向方法是module.exports = {對(duì)象} | exports = {對(duì)象},只要發(fā)
生了這個(gè)操作百姓,就確定了指向哪個(gè)對(duì)象渊额。當(dāng)然,module.exports.屬性/方法 |
exports.屬性/方法瓣戚,這樣子添加屬性或方法并不會(huì)改變指向端圈。
你可以這樣:
module.exports.name = 'hello';
exports.age = 10;
module.exports.print = function(){console.log(12345)};
如果只是使用.來添加屬性和方法,module.exports和exports混用是完全可以的.
也可以這樣:
module.exports = {
name : 'hello'
};
exports.age = 10;
module.exports.print = function(){console.log(12345)};
但不可以這樣:
module.exports = {
name : 'hello'
};
exports = {age:10}; // exports現(xiàn)在是{age:10}這個(gè)對(duì)象的引用子库,不再是
module.exports的引用了
console.log(module); //你會(huì)看到Module的exports中只有name屬性2杖ā!仑嗅!
也不可以這樣:
exports.age = 10;
console.log(module); //你會(huì)看到Module的exports中多了age屬性
module.exports = {
name : 'hello'
};
console.log(module);
//執(zhí)行結(jié)果
Module {
id: '.',
exports: { age: 10 },
parent: null,
filename: 'F:\\fore-end\\materials\\Node.js\\projs\\proj2\\test.js',
loaded: false,
children: [],
paths:
[ 'F:\\fore-end\\materials\\Node.js\\projs\\proj2\\node_modules',
'F:\\fore-end\\materials\\Node.js\\projs\\node_modules',
'F:\\fore-end\\materials\\Node.js\\node_modules',
'F:\\fore-end\\materials\\node_modules',
'F:\\fore-end\\node_modules',
'F:\\node_modules' ] }
Module {
id: '.',
exports: { name: 'hello' },
parent: null,
filename: 'F:\\fore-end\\materials\\Node.js\\projs\\proj2\\test.js',
loaded: false,
children: [],
paths:
[ 'F:\\fore-end\\materials\\Node.js\\projs\\proj2\\node_modules',
'F:\\fore-end\\materials\\Node.js\\projs\\node_modules',
'F:\\fore-end\\materials\\Node.js\\node_modules',
'F:\\fore-end\\materials\\node_modules',
'F:\\fore-end\\node_modules',
'F:\\node_modules' ] }
現(xiàn)在就很明白了宴倍,改變exports的指向后所添加的exports.xxx都是無效的。因?yàn)?/p>
require返回的只會(huì)是module.exports仓技。
require使用規(guī)則
require加載文件可以省略擴(kuò)展名
require不僅僅可以載入JS模塊鸵贬,也可以載入JSON對(duì)象(JSON文件,大部分用于讀取配置信息)
----require的參數(shù)如果不以“./”或“/”開頭脖捻,則表示加載的是一個(gè)默認(rèn)的核心模塊阔逼,比如require('fs')加載核心模塊中的文件系統(tǒng)模塊
一旦出現(xiàn)模塊名重復(fù),系統(tǒng)模塊的優(yōu)先級(jí)最高
自己寫的模塊只要放在node_modules文件夾下地沮,就可以像node自己的核心模塊一樣嗜浮,require不用寫路徑羡亩,直接寫模塊名來加載
模塊的緩存
第一次加載某個(gè)模塊的時(shí)候,node會(huì)緩存該模塊危融,以后再加載該模塊畏铆,就直接從
緩存取出該模塊的module.exports屬性
cache對(duì)象
cache對(duì)象里面有node執(zhí)行后的所有緩存
刪除緩存
Object.keys(require.cache).forEach((key) =>{
delete require.cache[key];
} )
當(dāng)然,一般我們不需要手動(dòng)清空node的緩存吉殃。
如果我們不去手動(dòng)刪除緩存辞居,又想每次require加載模塊都執(zhí)行模塊中的代碼,比如
下面這種
//time.js
console.log('模塊的的代碼')蛋勺;
module.exports = new Date();
//index.js
setInterval(() => {
let date = require('./time.js');
console.log(date.getTime());
}, 1000);
因?yàn)閚ode的緩存瓦灶,第一次載入模塊,執(zhí)行了time.js中的代碼迫卢,而接下來執(zhí)行載入
模塊的時(shí)候倚搬,不會(huì)真的去調(diào)用time.js文件冶共,而是從緩存中調(diào)入緩存的該模塊(其實(shí)
是該模塊上次執(zhí)行結(jié)果的緩存)
執(zhí)行結(jié)果
模塊的的代碼
1512556841717
//第一次執(zhí)行結(jié)束乾蛤,打印了‘模塊的的代碼’,創(chuàng)建了一個(gè)Date對(duì)象捅僵,并將這些結(jié)
果存入緩存
1512556841717
1512556841717
1512556841717
1512556841717
1512556841717
1512556841717
...
我們?cè)撊绾翁幚磉@個(gè)問題呢家卖?
很簡單,反正都是沖緩存中取出這個(gè)模塊庙楚,如果緩存的是一個(gè)方法上荡,那么每次從緩
沖中取出的都是這個(gè)方法,取出方法之后都去執(zhí)行一下馒闷,不就是每次都重新執(zhí)行了
一下方法中的代碼嗎酪捡,所以將我們的代碼放在一個(gè)方法中并導(dǎo)出,修改如下
//time.js
module.exports = () => {
console.log('模塊的的代碼');
return new Date();//這個(gè)方法返回一個(gè)Date對(duì)象
}
//index.js
setInterval(() => {
let date = require('./error.js');
console.log(date().getTime());//date是個(gè)函數(shù)纳账,執(zhí)行一下逛薇,返回Date
對(duì)象
}, 1000);
執(zhí)行結(jié)果
模塊的的代碼
1512557968297
模塊的的代碼
1512557969303
模塊的的代碼
1512557970304
模塊的的代碼
1512557971306
模塊的的代碼
1512557972308
模塊的的代碼
1512557973310
...