二. 模塊化
Node.js所有的API都是基于模塊發(fā)布和使用的,因此在真正的學習Node.js之前照弥,我們需要先了解模塊化開發(fā)这揣。
2.1 模塊化的由來
JavaScript誕生初期,不像成熟的面向對象語言(如Java)擁有與生俱來的模塊系統(tǒng)机打,甚至沒有人把它當做一門真正的編程語言片迅。
JavaScript初期引入模塊的方式都是通過script標簽來引入代碼,但這樣很容易產(chǎn)生如下問題:
全局的變量污染芥挣,并且無法找到源頭耻台。
過多的script標簽會造成雜亂無章的代碼。
使用某個庫蹋砚,卻爆出沒有相關庫的依賴。
無法追蹤的錯誤和報錯位置娇豫。
為了解決JavaScript模塊化問題畅厢,程序員們一開始采用「命令空間」的方式來約束代碼框杜,讓看起來凌亂的編程現(xiàn)狀有所緩解。但是這僅僅屬于一種約定振劳,并沒有從技術上解決根本問題油狂。
2.2 CommonJS規(guī)范
Node.js首先采用了CommonJS規(guī)范,CommonJS并非一門新的API弱贼,也不是標準模塊系統(tǒng)磷蛹,只不過是目前Node.js使用最廣泛的模塊系統(tǒng)(規(guī)范)味咳。CommonJS 規(guī)范是為了解決 JavaScript 的作用域問題而定義的模塊形式,可以使每個模塊它自身的命名空間中執(zhí)行槽驶。
2.2.1 CommonJs 基本使用規(guī)則
該規(guī)范的主要內容是:
每個js文件就是一個模塊掂铐,模塊有自己的作用域。在一個文件里面定義的變量聂受、函數(shù)烤镐、類棍鳖,都是私有的,對其他文件不可見镜悉。
每個模塊內部侣肄,module變量(默認擁有)代表當前模塊。這個變量是一個對象吼具,它的exports屬性(即module.exports)是對外的接口矩距。加載某個模塊,其實是加載該模塊的module.exports屬性陡蝇。
模塊必須通過 module.exports 導出對外的變量或接口哮肚。
// moduleA.js
varx=5;
varaddX=function(value) {
returnvalue+x;
};
module.exports.x=x;
module.exports.addX=addX;
通過 require() 來導入其他模塊的輸出到當前模塊作用域中绽左。
// moduleB.js
varexample=require('./moduleA.js');
console.log(example.x);// 5
console.log(example.addX(1));// 6
CommonJS模塊的特點如下。
所有代碼都運行在模塊作用域戏蔑,不會污染全局作用域鲁纠。
模塊可以多次加載,但是只會在第一次加載時運行一次情龄,然后運行結果就被緩存了捍壤,以后再加載鹃觉,就直接讀取緩存結果。要想讓模塊再次運行祷肯,必須清除緩存。
模塊加載的順序翼闹,按照其在代碼中出現(xiàn)的順序蒋纬。
2.2.2 內部實現(xiàn)原理
正如上面所說,node執(zhí)行js文件時會默認把文件代碼封裝到一個module變量中法牲,它是Module的一個實例琼掠。
functionModule(id,parent) {
this.id=id;
this.exports={};
this.parent=parent;
// ...
}
每個模塊內部瓷蛙,都有一個module對象,代表當前模塊横堡。它有以下屬性:
module.id 模塊的識別符冠桃,通常是帶有絕對路徑的模塊文件名食听。
module.filename 模塊的文件名,帶有絕對路徑葬项。
module.loaded 返回一個布爾值迹蛤,表示模塊是否已經(jīng)完成加載。
module.parent 返回一個對象嚷量,表示調用該模塊的模塊逆趣。
module.children 返回一個數(shù)組汗贫,表示該模塊要用到的其他模塊。
module.exports 表示模塊對外輸出的值部蛇。
嘗試執(zhí)行以下代碼:
// example.js
varjquery=require('jquery');
exports.$=jquery;
console.log(module);
打印信息:
{id:'.',
exports: {'$': [Function] },
parent:null,
filename:'/path/to/example.js',
loaded:false,
children:
[ {id:'/path/to/node_modules/jquery/dist/jquery.js',
exports: [Function],
parent: [Circular],
filename:'/path/to/node_modules/jquery/dist/jquery.js',
loaded:true,
children: [],
paths: [Object] } ],
paths:
['/home/user/deleted/node_modules',
'/home/user/node_modules',
'/home/node_modules',
'/node_modules']
}
module.exports屬性表示當前模塊對外輸出的接口咐蝇,其他文件加載該模塊有序,實際上就是讀取module.exports變量。為了方便警绩,Node為每個模塊提供一個exports變量肩祥,指向module.exports混狠。這等同在每個模塊頭部疾层,有一行這樣的命令。
varexports=module.exports;
可以直接使用exports予弧,但注意湖饱,不能直接將exports變量指向一個值琉历,因為這樣等于切斷了exports與module.exports的聯(lián)系:
exports.area=function(r) {
returnMath.PI*r*r;
};
exports.circumference=function(r) {
return2*Math.PI*r;
};
exports=function(x) {console.log(x)};//這樣做不行,因為相當于在文件內部定義一個私有變量了彪置,通過require無法拿到
CommonJS加載是同步的蝇恶,因此使用CommonJS無法做到按需加載撮弧。
2.3 ES6中的模塊化
ES6模塊是ECMA組織從語言層面提出的標準模塊化API姚糊,未來完全可以取代CommonJS和其他的模塊化系統(tǒng)授舟。
2.3.1 基本使用方式
ES6模塊的語法完全不同于CommonJS释树,因為ES6 模塊不是對象,而是通過export命令顯式指定導出的代碼秸仙,再通過import命令導入:
// person.js
exportdefaultconstperson={}
exportage=30
使用
importperson,{age}from'person'
2.3.2 模塊的加載原理
import 方式導入模塊是基于“編譯時加載”或者靜態(tài)加載桩盲,即 ES6 可以在編譯時就完成模塊加載赌结,效率要比 CommonJS 模塊的加載方式高。
它具有如下優(yōu)勢:
基于編譯時加載襟交,可以讓模塊在編譯階段就能進行靜態(tài)語法分析伤靠,從而提前預警語法錯誤或者做類型校驗宴合。
未來Node.js和瀏覽器都將支持,可以一統(tǒng)天下贞言。
ES6模塊默認在嚴格模式下執(zhí)行阀蒂,即默認在模塊頭部加入:"use strict"。
## 附:嚴格模式主要有以下限制酗失。
- 變量必須聲明后再使用
- 函數(shù)的參數(shù)不能有同名屬性昧绣,否則報錯
- 不能使用`with`語句
- 不能對只讀屬性賦值,否則報錯
- 不能使用前綴 0 表示八進制數(shù)删壮,否則報錯
- 不能刪除不可刪除的屬性兑牡,否則報錯
- 不能刪除變量`delete prop`,會報錯发绢,只能刪除屬性`delete global[prop]`
- `eval`不會在它的外層作用域引入變量
- `eval`和`arguments`不能被重新賦值
- `arguments`不會自動反映函數(shù)參數(shù)的變化
- 不能使用`arguments.callee`
- 不能使用`arguments.caller`
- 禁止`this`指向全局對象
- 不能使用`fn.caller`和`fn.arguments`獲取函數(shù)調用的堆棧
- 增加了保留字(比如`protected`边酒、`static`和`interface`)
尤其注意:頂層的this指向undefined狸窘,不應該在頂層代碼使用this翻擒。
2.3.3 詳解import和export命令
模塊功能主要由兩個命令構成:export和import。
export命令用于規(guī)定模塊的對外接口劳吠。
一個模塊就是一個獨立的文件巩趁。該文件內部的所有變量议慰,外部無法獲取,除非通過export導出:
// profile.js
exportvarfirstName='Michael';
exportvarlastName='Jackson';
exportvaryear=1958;
或者寫成:
// profile.js
varfirstName='Michael';
varlastName='Jackson';
varyear=1958;
?
export{firstName,lastName,yearasage};
export 中的as關鍵字可以重命名導出的變量名草讶。
export無法直接導出值或變量:
// 報錯
export1;
// 報錯
varm=1;
exportm;
?
// 下面是正確的寫法
?
// 寫法一
exportvarm=1;
// 寫法二
varm=1;
export{m};
// 寫法三
varn=1;
export{nasm};
export必須在頂層作用域使用炉菲,無法在其他塊級作用域使用:
functionfoo() {
exportdefault'bar'// 語法錯誤
}
foo()
export default 命令可以導出默認的變量拍霜,import使用時可以指定任意名字
exportdefaultfunctionabc(){}
或者
functionabc(){}
export{abcasdefault}
使用
importcustomNamefrom'./export-default.js'
或者
import{defaultasabc}from'./export-default.js'
import`命令用于輸入其他模塊提供的功能。
export導出的接口都通過import來導入屿讽,import中也可以通過as對導入的變量進行重命名伐谈。
import{lastNameassurname}from'./profile.js';
import導入的變量都是只讀的,無法修改抠蚣。
import{a}from'./xxx.js'
?
a={};// Syntax Error : 'a' is read-only;
import 會導致導入的模塊自動加載執(zhí)行履澳,并且多次重復執(zhí)行同一句import語句距贷,只會執(zhí)行一次:
import'lodash';
import'lodash';//這句就不執(zhí)行了
模塊整體加載可以使用*號:
import*aspersonfromperson
import()方法的動態(tài)加載
import命令是基于編譯時加載,如果想使用運行時加載該怎么辦现横?
可以使用全局的import()方法阁最,注意:該方法是ES6提案中引入的方法速种,和import命令完全不是一碼事。
import()方法支持傳入一個模塊路徑贩据,返回一個Promise闸餐,在then方法中可以拿到模塊的引用舍沙。
import('./dialogBox.js')
.then(dialogBox=>{
dialogBox.open();
? })
.catch(error=>{
/* Error handling */
? })
2.4 其他模塊化解決方案
除了CommonJS被Node.js定為官方模塊化標準,以及ES6 Module被ECMA組織定為瀏覽器支持的原生標準外壹无,在模塊化探索年代還產(chǎn)生過一些其他的模塊化規(guī)范感帅,不過隨著時間的流逝失球,這些都被人們廢棄了帮毁。
2.4.1 AMD規(guī)范
require.js實現(xiàn)了AMD規(guī)范的
在ES6模塊化方案提出之前豺撑,瀏覽器實現(xiàn)異步加載模塊聪轿,都是基于AMD規(guī)范來實現(xiàn),其寫法如下:
// lib模塊
define(['package/lib'],function(lib){
functionhello(){
lib.log('hello world!');
? }
?
return{
foo:foo
? };
});
使用模塊:
require(['lib'],function(lib) {
lib.foo()
});
AMD規(guī)范必須一次性把所有依賴加載完畢灯抛,也無法做到按需加載对嚼,但可以做到異步加載外莲。
2.4.2 CMD規(guī)范
sea.js實現(xiàn)了CMD規(guī)范
CMD規(guī)范是AMD規(guī)范的一個變種偷线,為了讓AMD規(guī)范兼容CommonJS的風格沽甥。
define(function(require,exports,module) {
var$=require('jquery');
require.async(['./b','./c'],function(b) {
b.doSomething();
c.doSomething();
? });
exports.doSomething=...
module.exports=...
})
使用模塊也是按照如上的方式使用摆舟。
CMD相比AMD可以做到異步加載亥曹,但目前也幾乎每人使用,因為書寫太麻煩恨诱,而且ES6已經(jīng)把模塊規(guī)范化媳瞪。
三. Node.js常用的內置模塊
Node.js中提供了一些原生的模塊,我們稱之為內置模塊照宝。此外蛇受,也可以通過NPM命令安裝第三方模塊,安裝完畢后使用方式和內置模塊沒有什么不同厕鹃。
Node.js中的原生模塊直接通過require就能獲得,參考Node.js API文檔來使用這些原生模塊剂碴。接下來把将,針對一些常用模塊進行介紹。
3.1 fs模塊
fs 模塊以 POSIX 標準函數(shù)的方式提供了與文件系統(tǒng)進行交互的API忆矛,fs中的每個方法都分同步和異步兩種方式調用察蹲。通過fs模塊可以獲取一個目錄結構,及其子目錄或目錄樹下文件的特征,甚至可以讀寫递览、復制或者刪除文件叼屠。
什么是POSIX標準
可移植操作系統(tǒng)接口(Portable Operating System Interface of UNIX,縮寫為 POSIX )绞铃,POSIX標準定義了操作系統(tǒng)應該為應用程序提供的接口標準镜雨,保證了接口的可移植。
fs模塊中操作文件或文件夾的方式可以基于:同步儿捧、異步回調或者異步Promise三種模式荚坞。還記得Promise嗎?
文件夾讀取示例三種方式
constfs=require('fs')
// 使用同步的方式
constdirent=fs.readdirSync(__dirname)
console.log(dirent)
// 使用異步回調的方式
fs.readdir(__dirname, (err,dirent)=>{
if(err) {
console.error(err)
}else{
console.log(dirent)
?? }
})
// 使用Promise的方式菲盾,目前還不穩(wěn)定
fs.promises.readdir(__dirname).then((dirent)=>{
console.log(dirent)
}).catch((err)=>{
console.error(err)
})
和文件系統(tǒng)有關的全局變量
__dirname是Node.js提供的全局變量颓影,表示當前正在執(zhí)行的代碼文件所在的目錄絕對路徑。
__filename表示當前正在執(zhí)行的代碼文件的絕對路徑懒鉴。
注意以上變量和執(zhí)行環(huán)境是無關的诡挂,只和代碼文件的保存位置有關。
在不同路徑的js文件中加入下面代碼临谱,通過node命令執(zhí)行看看
console.log(__dirname)
console.log(__filename)
3.1.1 文件夾的操作
fs.access():檢查文件或文件夾是否存在璃俗。
fs.readdir():讀取文件夾。
fs.mkdir():創(chuàng)建文件夾悉默,如果文件夾已存在會導致錯誤城豁。
fs.rmdir():刪除文件夾,如果文件夾內部有子文件會導致錯誤抄课。
fs.rename():修改文件或文件夾名字唱星。
constfs=require('fs')
// 以utf-8格式讀取目錄的子文件名字
fs.readdir('/Users', {encoding:'utf-8'}, (err,files)=>{
if(err) {
console.error(err)
}else{
console.log(files)
?? }
})
// 檢查文件是否存在
fs.access(__dirname+'/temp',fs.constants.F_OK, (err)=>{
if(err) {
// 報錯表示不存在
// 創(chuàng)建文件夾
fs.mkdir(__dirname+'/temp', (err)=>{
if(err) {
console.error(err)
}else{
setTimeout(()=>{
// 2秒之后修改文件夾名為temp2
fs.rename(__dirname+'/temp',__dirname+'/temp2', (err)=>{
if(err)console.error(err)
? ? ? ? ? ? ? ? ?? })
},2000);
? ? ? ? ?? }
?
? ? ?? })
}else{
// 刪除文件夾
fs.rmdir(__dirname+'/temp', (err)=>{
if(err) {
console.error(err)
? ? ? ? ?? }
? ? ?? })
?? }
})
3.1.2 文件的操作
fs.writeFile():讀取或者創(chuàng)建文件。
fs.unlink():刪除文件及其文件鏈接(快捷方式)跟磨。
constfs=require('fs')
// 新建文件
fs.writeFile(__dirname+'/text.txt','', (err)=>{
if(err)throwerr
setTimeout(()=>{
// 刪除文件
fs.unlink(__dirname+'/text.txt', (err)=>{
if(err)throwerr
? ? ?? })
},3000);
})
3.1.3 文件屬性的獲取
通過fs.lstat()方法可以讀取文件屬性间聊,文件屬性保存在一個fs.Stats對象里面。
constfs=require('fs')
// 獲取文件屬性
fs.lstat(__filename, (err,stats)=>{
if(err)throwerr
if(stats.isFile) {
console.log(`${__filename}是文件,創(chuàng)建于${stats.birthtime},大小${stats.size}`)
?? }
})
3.1.4 大文件的讀寫
對于比較大型的文件(如視頻文件)抵拘,拷貝文件或讀取文件是分塊的以流的方式來讀取的哎榴,因為直接讀取整個文件會很容易超過內存容量。
const stream = fs.createReadStream('c:\\demo\1.txt');
let data = ''
stream.on('data', (trunk) => {
? data += trunk;
});
stream.on('end', () => {
? console.log(data);
});
文件流的讀寫比較復雜仑濒,推薦直接使用第三方庫:cp-file
3.2 path模塊
path模塊也是Node.js中經(jīng)常使用的模塊叹话,主要用于路徑的處理,由于Windows和Unix路徑的格式有差異墩瞳,因此在不同平臺上調用的方法可能得到只針對平臺的路徑驼壶。盡量不要使用和平臺相關的path API。
path.dirname():返回所在目錄名喉酌。
path.dirname('/foo/bar/baz/asdf/quux');
// 返回: '/foo/bar/baz/asdf'
path.extname:返回擴展名热凹。
path.extname('index.html');
// 返回: '.html'
path.join:可以使用平臺特定的分隔符作為定界符將所有給定的 path 片段連接在一起泵喘,然后規(guī)范化生成的路徑。
path.join('/foo','bar','baz/asdf','quux','..');
// 返回: '/foo/bar/baz/asdf'
path.normalize() :方法規(guī)范化給定的 path般妙,解析 '..' 和 '.' 片段纪铺。
path.normalize('C:\\temp\\\\foo\\bar\\..\\');
// 返回: 'C:\\temp\\foo\\'
path.resolve() 方法將路徑或路徑片段的序列解析為絕對路徑。
規(guī)則:
給定的路徑序列從右到左進行處理碟渺,每個后續(xù)的 path 前置鲜锚,直到構造出一個絕對路徑。
如果在處理完所有給定的 path 片段之后還未生成絕對路徑苫拍,則再加上當前工作目錄芜繁。
生成的路徑已規(guī)范化,并且除非將路徑解析為根目錄绒极,否則將刪除尾部斜杠骏令。
constpath=require('path')
console.log(path.resolve('/foo','/bar','./baz'))
// '/bar/baz'
console.log(path.resolve('/foo/bar','./baz'))
// '/foo/bar/baz'
console.log(path.resolve('foo/bar','./baz/'))
// '/<當前執(zhí)行路徑>/foo/bar/baz'
3.4 url模塊
url模塊主要處理URL(統(tǒng)一資源定位符),URL的格式構成:
協(xié)議+認證+主機+端口+路徑+查詢字符串+哈希值垄提。
可以參照下圖:
url模塊主要使用以下兩個API接口:
url.format():負責把一個url的json對象轉換成合法的url地址高职。
url.parse():負責把一個url地址轉換成一個url的json對象膛腐。
consturl=require('url')
constpath=url.format({
protocol:'https',
hostname:'example.com',
pathname:'/some/path',
query: {
page:1,
format:'json'
?? }
});
console.log(path)
// => 'https://example.com/some/path?page=1&format=json'
console.log(url.parse(path))
// Url {
// ? protocol: 'https:',
// ? slashes: true,
// ? auth: null,
// ? host: 'example.com',
// ? port: null,
// ? hostname: 'example.com',
// ? hash: null,
// ? search: '?page=1&format=json',
// ? query: 'page=1&format=json',
// ? pathname: '/some/path',
// ? path: '/some/path?page=1&format=json',
// ? href: 'https://example.com/some/path?page=1&format=json' }
3.5 querystring模塊
通過url.parse轉換的url對象中的query對象是一個字符串浪规,如果想進一步拿到查詢字符串的鍵值對招刹,需要再通過querystring來轉換。
querystring.stringify():可以把查詢字符串對象轉換成字符串高蜂。querystring.stringify默認會對非ASCII字符進行百分比編碼聪黎,即內部調用querystring.escape() 方法罕容。
querystring.parse():可以把查詢字符串轉換成對象备恤。querystring.parse默認會對經(jīng)過百分比編碼的字符進行解碼操作,即內部調用了querystring.unescape() 锦秒。
constqs=require('querystring')
conststr=qs.stringify({
name:'小明',
age:30,
description: ['動物','人']
})
console.log(str)
// name=%E5%B0%8F%E6%98%8E&age=30&description=%E5%8A%A8%E7%89%A9&description=%E4%BA%BA
constobj=qs.parse(str)
console.log(obj)
// [Object: null prototype] { name: '小明', age: '30', description: ['動物', '人'] }
3.6 http模塊
http/https模塊是Node.js中和網(wǎng)絡請求相關的核心模塊露泊,類似瀏覽器中的ajax,但是它除了可以請求數(shù)據(jù)旅择,還可以通過它搭建HTTP(S)服務惭笑。
接下來,我們使用http模塊模擬一個爬蟲案例生真。
什么是爬蟲沉噩?
對于一個 害怕蟲子的開發(fā)者來說,估計選擇爬蟲行業(yè)可以是一個磨煉意志的機會柱蟀。
爬蟲就是一段自動抓取互聯(lián)網(wǎng)信息的程序川蒙,從互聯(lián)網(wǎng)上抓取對于我們有價值的信息。
事實上长已,搜索引擎就是一個巨型的爬蟲畜眨,它可以輔助我們完成信息的檢索和歸類昼牛,讓我們以最快的速度找到我們最想要的信息。
接下來康聂,我們只是簡單地使用https模塊和第三方cheer.io模塊來爬去某一個網(wǎng)站的有用信息贰健。
因為目前大部分網(wǎng)站都是基于https協(xié)議的,所以就是用https模塊恬汁,它和http協(xié)議最大的區(qū)別就是https會對請求和資源加密解密伶椿。
編寫http代碼,實現(xiàn)資源獲取氓侧。
varhttp=require('http')
varhttps=require('https')
functiongetUrlResource(url,callback) {
varclient
if(url.startsWith('https')) {
client=https
}elseif(url.startsWith('http')) {
client=http
}else{
callback(newError('只能請求http/https協(xié)議的內容'))
return;
?? }
client.get(url, (res)=>{
const{statusCode}=res;
constcontentType=res.headers['content-type'];
?
leterror;
if(statusCode!==200) {
error=newError('請求失敗\n'+
`狀態(tài)碼: ${statusCode}`);
}elseif(!/^text\/html/.test(contentType)) {
error=newError('無效的 content-type.\n'+
`期望的是 text/html 但接收到的是 ${contentType}`);
? ? ?? }
if(error) {
// 消費響應數(shù)據(jù)來釋放內存悬垃。
res.resume();
callback(error)
return;
? ? ?? }
?
res.setEncoding('utf8');
letrawData='';
res.on('data', (chunk)=>{rawData+=chunk; });
res.on('end', ()=>{
callback(null,rawData)
? ? ?? });
}).on('error', (e)=>{
callback(e)
?? });
}
編寫爬蟲代碼
varcheerio=require('cheerio')
getUrlResource('https://www.baidu.com/',function(e,data) {
if(e) {
console.error(e)
return
?? }
?
var$=cheerio.load(data)
var$imgs=$('img[src]')
if($imgs.length===0) {
console.log('沒有找到圖片資源')
return
?? }
// 注意執(zhí)行get才能拿到數(shù)組
varimgSrcValues=$imgs.map(function(i,el) {
return$(el).attr('src');
}).get()
varimgUrls=imgSrcValues.join(',\n');
console.log(imgUrls)
})
四. Express框架
Express 是Node.js開發(fā)中使用最廣泛的服務器框架,通過它可以快速的搭建一個Web容器并向外界提供Web服務甘苍。
Express基于Node.js的http/https搭建服務器尝蠕,通過中間件的即插即用方式來擴展功能。
4.1 基本使用
創(chuàng)建一個項目文件夾载庭,命名為express-demo并通過cd命令進入到express-demo目錄看彼。
使用npm init -y初始化項目,生成package.json項目描述文件囚聚。
使用npm install -save express安裝express靖榕。
創(chuàng)建一個index.js文件,寫入以下代碼訪問瀏覽器即可查看效果顽铸。
constexpress=require('express')
constapp=express()
app.get('/', (req,res)=>res.send('你好!'))
app.listen(3000, ()=>{
console.log('服務器創(chuàng)建完畢,監(jiān)聽3000端口')
})
打開終端茁计,在express-demo目錄下執(zhí)行:
nodeindex.js。
此外谓松,如果希望不在console中調試星压,而是在Chrome中通過dev tools調試,可以使用:
node--inspectindex.js
然后打開開發(fā)者工具鬼譬,里面有個Node圖標娜膘,點擊即可調試。
假如你希望每一次修改代碼都自動重啟Express优质,可以全局安裝nodemon竣贪,它和node的命令幾乎一樣。
npm install -g nodemon
nodemon --inspect index.js
打開瀏覽器巩螃,輸入:http://本機ip/ 查看效果演怎。
為了方便自動的檢測本機ip和自動打開瀏覽器測試,可以安裝兩個第三方npm包:open和address避乏,portFinder爷耀。然后服務器啟動成功之后可以自動打開瀏覽器測試。
npminstall-saveopen address portfinder
constexpress=require('express')
consturl=require('url')
constopen=require('open')
constaddress=require('address')
constportfinder=require('portfinder')
constapp=express()
app.get('/', (req,res)=>res.send('你好!'))
// 防止3000端口被占用
portfinder.getPort({
port:3000,// minimum port
stopPort:3333// maximum port
},function(err,port){
if(err){
thrownewError('沒有找到可以啟動的端口')
? }
app.listen(port, ()=>{
console.log('服務器創(chuàng)建完畢,監(jiān)聽'+port+'端口')
? // 自動打開Chrome瀏覽器加載網(wǎng)頁
open(url.format({
protocol:'http',
port:port,
hostname:address.ip(),pathname:'/'
}), {app: ['google chrome','--incognito'] })
? });
});
可以把node index.js命令加入到package.json的scripts字段里面淑际,通過npm start啟動畏纲。
{
"name":"express-demo",
"main":"index.js",
"scripts": {
"start":"node index.js"
? },
"dependencies": {
"address":"^1.1.2",
"express":"^4.17.1",
"open":"^7.0.0"
? }
}
npmstart