前端點滴(Node.js)(二)

Node.js

一、包與npm命令

1. 使用moment

項目目錄下打開終端,輸入命令,等待下載:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

在代碼中引入、使用:
在這裏插入圖片描述
在這裏插入圖片描述

結果:
在這裏插入圖片描述

2. npm 命令的使用

上面的代碼,我們使用npm安裝了moment來進行格式化時間的處理,這就是使用第三方模塊;
在這裏插入圖片描述

而我們使用的npm就是node中自帶的包(模塊)管理工具;

藉助NPM可以幫助我們快速安裝和管理依賴包,使Node與第三方模塊之間形成了一個良好的生態系統;
在這裏插入圖片描述

我們也可以直接輸入npm,查看幫助引導:
在這裏插入圖片描述

3. 包的初始化

一個項目,不可能只是使用一個第三方包,而包越多,管理起來就越麻煩,

而 npm init 給我們提供了項目初始化的功能,也解決了多個包的管理問題:
在這裏插入圖片描述

一直按回車鍵enter

最後:
在這裏插入圖片描述

內容:

{          
  "name": "test",   // 項目名
  "version": "1.0.0",   // 版本號 
  "main": "http.js",   // 入口文件      
  "dependencies": {  // 用戶發佈環境,生成上所需要的依賴包     
    "moment": "^2.24.0" // 包名以及版本   
  },
  "devDependencies": {},  // 用於本地環境開發時候所需要的依賴包      
  "scripts": {   // npm 設置的一些指令
    "test": "echo \"Error: no test specified\" && exit 1"
  },         
  "author": "",   // 作者        
  "license": "ISC",  // 當前項目的協議    
  "description": ""   //項目描述 
}   

dependencies與devDependencies的區別:

  • 後面部分爲–save -dev 的情況會使得下載的插件放在package.json文件的devDpendencies對象裏面。

  • 後面部分爲–save的情況會使得下載的插件放在package.json文件的dependencies對象裏面。

  • devDependencies下的依賴包,只是我們在本地或開發壞境下運行代碼所依賴的,若發到線上,其實就不需要devDependencies下的所有依賴包;(比如各種loader,babel全家桶及各種webpack的插件等)只用於開發環境,不用於生產環境,因此不需要打包;

  • dependencies是我們線上(生產壞境)下所要依賴的包,比如vue,我們線上時必須要使用的,所以要放在dependencies下;dependencies依賴的包不僅開發環境能使用,生產環境也能使用。

4. 包的結構

包實際上就是一個儲存文件,即一個目錄直接打包成爲.zip或者tar.gz格式的文件,安裝後解壓還原爲目錄。完全符合規範的包目錄應該包含如下文件:

  • package.json:包描述文件
  • bin:用於存放二進制文件的目錄
  • lib:用於存放JavaScript代碼的目錄
  • doc:用於存放文檔的目錄
  • test:用於存放單元測試實例的代碼目錄

5. 解決 npm 被牆問題

npm 存儲包文件的服務器在國外,有時候會被牆,速度很慢,所以我們需要解決這個問題。

http://npm.taobao.org/ 淘寶的開發團隊把 npm 在國內做了一個備份。

安裝淘寶的 cnpm:

# 在任意目錄執行都可以
# --global 表示安裝到全局,而非當前目錄
# --global 不能省略,否則不管用
npm install --global cnpm

接下來你安裝包的時候把之前的 npm 替換成 cnpm

舉個例子:

# 這裏還是走國外的 npm 服務器,速度比較慢
npm install moment

# 使用 cnpm 就會通過淘寶的服務器來下載 jquery
cnpm install moment

如果不想安裝 cnpm 又想使用淘寶的服務器來下載:

npm install jquery --registry=https://registry.npm.taobao.org

但是每一次手動這樣加參數很麻煩,所我們可以把這個選項加入配置文件中:

# 配置到淘寶服務器
npm config set registry https://registry.npm.taobao.org

# 查看 npm 配置信息
npm config list

只要經過了上面命令的配置,則你以後所有的 npm install 都會默認通過淘寶的服務器來下載。
在這裏插入圖片描述

6. package.json 與 package-lock.json 文件

如果後期開發過程中,需要項目遷移,我們只需要將package.json文件遷移即可,在新項目下執行npm install,根據package.json文件再次下載所需的依賴包。

兩者的區別:

  • package.json文件
    • 管理和記錄了你項目正在使用的第三方插件
  • package-lock.json文件
    • 詳細記錄了每個插件的內容,地址,唯一碼

當我們使用npm管理包時,package.json 及package-lock.json 的內容都會自動更新。

7. 服務端頁面渲染

之前的案例中,我們時通過前端瀏覽器發送ajax請求獲取服務器數據的,前端獲取數據後進行遍歷展示;

流程圖:
在這裏插入圖片描述

缺點就是發送多次請求、不利於搜索引擎查找;我們修改改爲後端渲染數據;

什麼叫後端渲染?

客戶端發送請求,服務端獲取所有數據,讀取index.html並將數據組合在html中一起響應給客戶端,瀏覽器進行渲染顯示。

如何實現?

使用第三方模塊 art-template: https://www.npmjs.com/package/art-template

先來做一個小實驗,瞭解art-template的使用方法:

  1. npm install art-template

JavaScript:(art-test.js)

/* 2.引入art-template */
var art = require('art-template');
/* 3.設置當前路徑 */
art.defaults.root = './';
var html = art('./art-test.html',{data:[{name:123,age:345},{a:678,b:987}]});
console.log(html);

HTML:(art-test.html)

<body>
    <h1>nihoa</h1>
    <h2>{{data[0].name}}</h2>
</body>

結果:
在這裏插入圖片描述

實現原理就是先獲取 ./art-test.html,根據 ./art-test.html中的{{data[0].name}}組合數據,最後整體響應。

重構案例

  1. 重新創建目錄,並初始化項目: npm init
  2. 將之前寫好的後臺文件 http.js 和 前臺模板頁面 index.html 複製到新項目目錄中;
  3. 安裝時間處理模塊: npm install moment
  4. 安裝模板引擎模塊: npm install art-template
  5. 修改 後臺文件 http.js 和 前臺模板頁面 index.html 文件

修改JavaScript:(http.js)

/* 引入核心模塊 */
var http = require('http');
var fs = require('fs');
/* 第三方模塊 */
var moment = require('moment');
var template = require('art-template');
/* 設置爲當前路徑 */
template.defaults.root = './';

/* 創建服務器 */
var server = http.createServer();
server.listen(8080,function(){
    console.log('啓動成功');
});
server.on('request',function(req,res){
    /* 獲取請求url */
    var urls = req.url;
    if(urls == '/'){
    	/* 此處獲取當前路徑下的所有文件各自的信息 */
        /* 以一個數組的形式存在 */
        /**
        *[
        *{name:'http.js',mtime:'2019-09-04T05:18:11.635Z',size:'866'}
        *{name:'index.html',mtime:'2019-09-04T05:18:11.635Z',size:'866'}
        *...
        *]
        */
        /* 讀取當前路徑下的文件夾名 */
        fs.readdir('./','utf8',function(err,data){
            //定義一個空數組
            var arr = [];
            var cont = 0;
            /* 循環遍歷每一個文件名,獲取其中的信息 */
            for(let i = 0;i<data.length;i++){
               	//每一個文件一個對象,用於保存信息
                arr[i] = {};
                /* for循環中存在異步,解決方法閉包 */
                (function(i){
                    /* 獲取每個文件的所有信息 */
                    fs.stat(data[i],function(err,datas){
                        /* 分清是文件還是文件夾,自定義一個type屬性,響應式用於分別圖標 */
                        if(datas.isFile()){//用於判斷是否是文件
                        	arr[i].type = 'text';             
                        }else{
                            arr[i].type = 'folder'
                        }
                        arr[i].name = data[i];
                        arr[i].mtime = moment(datas.mtime).format('YYYY-MM-DD hh:mm:ss');
                        arr[i].size = datas.size;
                        cont++;
                        if(cont == data.length){
                           	/* 獲取index.html,組合所有數組,整體響應 */
                           	var html = template('./index.html',{data:arr});
                           	res.end(html);
                        }
                    })
                })(i)
            }
        })
    }else{
    	fs.readFile('.' + req.url, function (err, data) {
            res.end(data);
        })
    }
})

修改HTML:(index.html)

<body>
   <h2>Index of /框架</h2>
   <table>
      <tbody>
         <tr>
            <th valign="top"><img src="./img/blank.gif" alt="[ICO]"></th>
            <th><a href="http://localhost/%e6%a1%86%e6%9e%b6/?C=N;O=D">Name</a></th>
            <th><a href="http://localhost/%e6%a1%86%e6%9e%b6/?C=M;O=A">Last modified</a></th>
            <th><a href="http://localhost/%e6%a1%86%e6%9e%b6/?C=S;O=A">Size</a></th>
            <th><a href="http://localhost/%e6%a1%86%e6%9e%b6/?C=D;O=A">Description</a></th>
         </tr>
         <tr>
            <th colspan="5">
               <hr>
            </th>
         </tr>
         <tr>
            <td valign="top"><img src="./img/back.gif" alt="[PARENTDIR]"></td>
            <td><a href="http://localhost/">Parent Directory</a> </td>
            <td>&nbsp;</td>
            <td align="right"> - </td>
            <td>&nbsp;</td>
         </tr>
         <!-- 使用模板語法 -->
         <!-- 循環模板
            {{each target}}
            {{$index}}表示循環下標 {{$value}}表示循環的值,也是一個對象,可以直接使用點語法
            {{/each}} 
         -->

         <!-- 判斷模板
         {{if value}} ... {{/if}}
         {{if v1}} ... {{else if v2}} ... {{/if}} 
         -->
         {{each data}}
         <tr>
            {{if $value.type == 'text'}}
            <td valign="top"><img src="./img/text.gif" alt="[DIR]"></td>
            {{else}}
            <td valign="top"><img src="./img/folder.gif" alt="[DIR]"></td>
            {{/if}}
            <td><a href="#">{{$value.name}}/</a> </td>
            <td align="right">{{$value.mtime}}</td>
            <td align="right">{{$value.size}}</td>
            <td>&nbsp;</td>'
         </tr>
         {{/each}}
         <tr>
            <th colspan="5">
               <hr>
            </th>
         </tr>
      </tbody>
   </table>
   <address>Apache/2.4.39 (Win64) PHP/7.2.18 Server at localhost Port 80</address>

</body>

效果:
在這裏插入圖片描述

那麼我們在項目中應該使用 客戶端渲染還是服務端渲染:

答:兩者都用,根據數據的不同作用而定;

推薦:

推舉一個node開發時使用的小工具 nodemon

npm install nodemon -g

安裝成功後,使用 nodemon 運行代碼,

代碼一旦被保存,nodemon便會自動重新運行新代碼

二、Node的模塊實現及CommonJS規範

1. CommonJS規範

(1)commomJs 的出發點

commonJs 規範的提出,主要是爲了彌補當前JavaScript沒有標準的缺陷,以達到像python、Java具有開發大型項目的基礎能力,而不是停留在小腳本程序階段。並且期望那些使用commonJs API寫出的應用可以具有跨宿主環境執行能力,這樣不經可以利用JavaScript開發客戶端應用,而且還能編寫以下應用。

  • 服務器端JavaScript應用程序。
  • 命令行工具
  • 桌面圖形界面應用程序
  • 混合應用

Electron 跨平臺的桌面應用框架: https://electronjs.org/

但是目前,他依舊在成長過程中,這些規範涵蓋了模塊、二進制、Buffer、字符集編碼、I/O流、進程環境、文件系統、套接字、單元測試、Web服務器網關接口、包管理等等。

What I’m describing here is not a technical problem. It’s a matter of people getting together and making a decision to step forward and start building up something bigger and cooler together.

我在這裏描述的不是一個技術問題。這是一個人們聚在一起,決定向前一步,開始一起建立更大更酷的東西的問題。

–Kevin Dangoor

下述是Node與瀏覽器以及W3C組織、commomJs組織、ECMAscript組織之間的關係:
在這裏插入圖片描述

(2)commomJs 的模塊化規範

commomJs對模塊的定義很簡單,只要分爲模塊引用,模塊定義,模塊的標識三部分

1. 模塊的引用

var fs = require('fs');

在commonJs規範中,存在require()方法,這個方法接受模塊的標識,從此引入一個模塊的API到當前上下文中

2. 模塊的定義與導出

在模塊中,上下文提供了require()方法來引入外部的模塊.對應引入的功能,上下文提供了exports對象用於導出當前模塊的方法或者變量,並且它是唯一導出的出口.在模塊中還存在一個module對象,它代表模塊自身,而exports是module的屬性.在node中,一個文件就是一個模塊,將方法掛載在exports對象上作爲屬性即可定義導出的方式:

// test.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
module.exports = { firstName, lastName, year };

在另一個文件中,我們通過require()方法引入模塊後就可以定義或者調用屬性與方法:

// demo.js
const test = require('./test.js');
console.log(test);  // {firstName: "Michael", lastName: "Jackson", year: 1958}

其實exports 對象就是module.exports 的引用; exports === module.exports

3. 模塊標識

模塊標識其實就是傳遞給require()方法的參數,它必須符合小駝峯命名法的命名法則,或者以 .或者…開頭的的相對路徑或者絕對路徑.(注意:它可以沒有後綴名.js)

重點注意 : 模塊中的方法和變量的作用於盡在模塊內部,每個模塊具有獨立的空間,互不干擾;

CommonJS 構建的模塊機制中的引入與導出是我們完全不用考慮變量污染或者替換的問題,相比與命名空間的機制,簡直就是天才和菜鳥的區別;

2. Node 的模塊實現

在Node中引入模塊,需要經歷以下三個步驟:

(1)路徑分析

(2)文件定位

(3)編譯執行

在Node中,模塊分爲兩類:1)Node提供的模塊,稱爲核心模塊;2)另一類是用戶編寫的模塊,稱爲文件模塊。

核心模塊:在node源碼編譯過程中,編譯進了二進制文件。在node進程啓動時,部分核心模塊就被直接加載進了內存中,所以這部分核心模塊引入時,文件定位和編譯執行這兩個步驟就可以省略,並且在路徑分析中優先判斷,所以加載速度是最快的。

文件模塊:在運行時動態加載,需要完整的路徑分析、文件定位、編譯執行過程,速度比核心模塊慢。

加載過程

(1)優先從緩存中加載

與前端瀏覽器會緩存靜態腳本文件以提高性能一樣。Node對引入過的模塊都會進行緩存,以減少二次引入時的開銷。不同的地方在於瀏覽器僅僅緩存文件,而node緩存的是編譯和執行後的對象。

不論是核心模塊還是文件模塊,require()方法對相同模塊的二次加載一律採用緩存優先的方式,只是第一優先級。不同的是核心模塊的緩存檢查優先於文件模塊的緩存檢查。

(2)路徑分析與文件的定位

無非就是模塊標識符的分析:

  • 核心模塊
  • . 或者 … 開頭的相對路徑文件模塊
  • 以/開頭的絕對路徑文件模塊
  • 非路徑形式的文件模塊,比如自定義模塊

第三方模塊的加載規則:

  • 先在當前文件的模塊所屬目錄去找 node_modules目錄
  • 如果找到,則去該目錄中找 模塊名的目錄 如 : moment
  • 如果找到 moment 目錄, 則找該目錄中的 package.json文件
  • 如果找到 package.json 文件,則找該文件中的 main屬性
  • 如果找到main 屬性,則拿到該屬性對應的文件
  • 如果找到 moment 目錄之後,
    • 沒有package.json
    • 或者有 package.json 沒有 main 屬性
    • 或者有 main 屬性,但是指向的路徑不存在
    • 則 node 會默認去看一下 moment 目錄中有沒有 index.js --> index.json–> index.node 文件
  • 如果找不到index 或者 找不到 moment 或者找不到 node_modules
  • 則進入上一級目錄找 node_moudles 查找(規則同上)
  • 如果上一級還找不到,繼續向上,一直到當前文件所屬磁盤的根目錄
  • 如果到磁盤概目錄還沒有找到,直接報錯

(3)模塊的編譯

各類文件的載入編譯方式:

  • .js文件的編譯。通過fs模塊同步讀取文件後進行編譯執行。

編譯執行過程:

1)頭尾包裝:

比如上述art-template的小實驗:

(funtion(exports,require,module,_filename,_dirname){
	var art = require('art-template');
	art.defaults.root = './';
	var html = art('./art-test.html',{data:[{name:123,age:345},{a:678,b:987}]});
	console.log(html);
})

2)通過vm原生模塊的runThisContext()方法執行(類似於eval,只是明確上下文,不會污染全局),返回一個具體的function對象。

3)最後將當前模塊對象的exports屬性,require()方法、module(模塊對象自身)、以及在文件定位中的得到的完整文件路徑和文件目錄作爲參數傳遞給這個function()執行。

  • .json文件的編譯。通過fs模塊同步讀取文件後,用JSON.parse()解析返回結果。

編譯執行過程:

直接調用JSON.parse()方法得到對象,然後將其複製給模塊對象的exports,以供外部調用。

  • 其他擴展名文件的編譯。它們都被當作.js文件載入。

3. 模塊化封裝案例

對服務端頁面渲染進行模塊化封裝:

修改JavaScript:(http.js)

http.js ---- 服務器啓動模塊,用於啓動服務器

/* 引入核心模塊 */
var http = require('http');
/* 引入router模塊 */
var router = require('./router')

/* 創建服務器 */
var server = http.createServer();
/* 將server傳到router.js中,監聽請求事件 */
router.server(server);

server.listen(8080,function(){
    console.log('啓動成功');
});

router.js ---- 路由模塊,用於接受請求並處理響應

var contrllor = require('./controller');
var fs = require('fs');

/* 創建一個函數供給http.js調用並傳參 */
function server(server) {
    server.on('request', function (req, res) {
        var urls = req.url;
        if (urls == '/') {
            /* 獲取數據 */
            var html = controller.html;
            res.setHeader('Content-Type', 'text/html;charset=utf-8');
            /* 響應數據 */
            res.end(html);
        }
        else {
            fs.readFile('.' + urls, function (error, data) {
                res.setHeader('Content-Type', 'text/html;charset=utf-8');
                res.end(data);
            })
        }
    });
}
/* 導出模塊,此處非常重要!!! */
exports.server = server;

controller.js ---- 業務邏輯模塊,用於獲取數據

var fs = require('fs');
var moment = require('moment');
var template = require('art-template');
template.defaults.root = './';

fs.readdir('./', 'utf8', function (err, data) {
    var arr = [];
    var cont = 0;
    for (var i = 0; i < data.length; i++) {
        arr[i] = {};
        (function (i) {
            fs.stat(data[i], function (err, datas) { 
                if (datas.isFile()) {
                    arr[i].type = 'text';
                } else {
                    arr[i].type = 'folder';
                }
                arr[i].name = data[i];
                arr[i].size = datas.size;
                arr[i].mtime = moment(datas.mtime).format('YYYY-MM-DD hh:mm');
                cont++;
                if (cont == data.length) {
                    var html = template('./index.html',{data:arr});
                    /* 導出數據 */
                    exports.html = html;
                }
            })
        })(i);
    }
})

效果:
在這裏插入圖片描述

發佈了37 篇原創文章 · 獲贊 6 · 訪問量 2207
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章