Mosh的Node.js教程(二)

前言javascript

本系列文章是根據Mosh大佬的視頻教程全方位Node開發 - Mosh整理而成,我的以爲視頻很是不錯,因此計劃邊學習邊整理成文章方便後期回顧。該視頻教程是英文的,可是有中文字幕,感謝marking1212提供的中文字幕翻譯。前端

本篇文章大綱

  • Node的模塊系統
  • 全局對象
  • Modules 模塊
  • 建立一個模塊
  • 加載一個模塊
  • 模塊包裝函數

Node的模塊系統

經過本篇文章的學習,你將瞭解什麼是模塊,爲何須要模塊,以及它們是怎麼工做的。java

Node內置核心模塊

  • 操做系統 os
  • 文件系統 fs
  • 事件 events
  • http

全局對象 Global Object

一般咱們會用console.log()在控制檯打印一些東西,這個console就是全局對象,它的做用域是全局,也就是說能夠在任何地方任何文件調用它。node

Node中也有不少全局對象。編程

在瀏覽器中,window對象表明的是全局對象。瀏覽器

當咱們調用console.log()的時候,實際上調用的是window.console.log(),固然咱們能夠直接省略前面的window.,JavaScript引擎會自動前置window關鍵字。bash

相似的你看到的其餘函數也屬於全局對象,好比setTimeout()clearTimeout()等,咱們能夠用window.setTimeout()來調用,也能夠直接調用。app

一樣的,當咱們聲明一個變量ide

var message = ''
複製代碼

這個變量一樣屬於window對象。模塊化

這裏要注意的是Node中並無window對象,它有個相似的對象global,咱們一樣能夠經過global調用setTimeout()或者其餘函數,也能夠直接省略global.調用。

還有一點要注意的是,這裏面定義的message變量並不會添加到global對象中,換句話說,好比你想打印console.log(global.message),你會在控制檯看到未定義的錯誤。

咱們在app.js文件輸入如下代碼:

var message = ''
console.log(global.message) // undefined
複製代碼

在控制檯會打印出undefined

global的做用域只在這個文件,也就是app.js文件中,在文件外它是不可見的,這就是Node的模塊化系統所致。

Modules 模塊

在客戶端,JavaScript是運行在瀏覽器中。

當咱們定義一個函數或變量,它的做用域是全局的,好比咱們定義一個sayHello函數

var sayHello = function (name) {
    
}
複製代碼

它的做用域是全局,能夠經過window對象訪問,但其實這種行爲邏輯有個問題,在真實的編程中,咱們常常講不一樣的代碼放到不一樣的文件中,也許在兩個文件中定義了同名的sayHello函數,由於函數被添加到全局變量,當咱們在另外一個文件中定義這個名字的函數,新的定義將會覆蓋舊的定義,這就是全局做用域的問題。

因此爲了創建可信和可維護的應用咱們應避免定義全局函數和變量,咱們應該模塊化。

咱們須要建立小型拼裝塊或者叫模塊來存放變量和函數,這樣不一樣模塊之間的同名函數和變量不會相互覆蓋,他們被封裝在模塊中了。

Node的核心概念就是模塊。

每一個Node中的文件都被看作模塊,每一個模塊中定義的變量和函數做用域僅在模塊內,以面向對象的觀點咱們叫它們私有成員,它們在容器外,也就是模塊外是不可見的。

若是你要在模塊外使用一個定義在模塊中的變量或函數,咱們須要明確的導出它爲公開成員。

每一個Node工程至少要包含一個文件或者說一個模塊。

這裏的app.js就是這個項目的主模塊,咱們把module對象打印出來看一下

console.log(module)
複製代碼

這個module對象看起來像全局對象,你可能會想經過全局對象global訪問它,可是實際上它不是全局對象。

回到控制檯,咱們運行node app.js,能夠看到打印出了module對象的詳細信息,它是一個JSON對象,包含了鍵值對,好比id,每一個模塊都有獨一無二的id。

Module {
  id: '.',
  exports: {},
  parent: null,
  filename: 'F:\\2020\\study\\Node.js\\node-course\\first-app\\app.js',
  loaded: false,
  children: [],
  paths:
   [ 'F:\\2020\\study\\Node.js\\node-course\\first-app\\node_modules',
     'F:\\2020\\study\\Node.js\\node-course\\node_modules',
     'F:\\2020\\study\\Node.js\\node_modules',
     'F:\\2020\\study\\node_modules',
     'F:\\2020\\node_modules',
     'F:\\node_modules' ] }
複製代碼

這裏,咱們先不用瞭解每一個屬性的意思,隨着課程的深刻咱們都會了解。

因此在Node中,每一個文件都是模塊,模塊中定義的成員做用域只在模塊中,它們在模塊外是不可見的。

建立一個模塊

咱們給應用添加一個模塊,新建一個文件logger.js,假設咱們爲記錄信息建立一個模塊,咱們要在不少地方複用這個模塊,有可能的話也會在別的應用複用。

在模塊中,咱們假設要使用一個遠程日誌服務來記錄咱們的日誌,有個其餘的網站能夠提供日誌服務,它提供了一個URL,能夠經過給它發送HTTP請求來記錄日誌。

咱們在logger.js寫一些代碼

var url = 'http://mylogger.io/log' // 這不是一個真實的地址,只作演示用,咱們就假設這個例子會向這個地址發送請求

function log(message) {
    console.log(message)
}
複製代碼

這個變量和這個函數的做用域都是這個文件,它們是私有的,在外部不可見。

可是,咱們想在app.js也就是咱們的主模塊中想用到日誌模塊,咱們應該要訪問log這個函數,咱們須要從app模塊調用它,咱們須要將它變爲公共的,能夠在外部訪問。

還記得module對象嗎,裏面有個exports屬性

Module {
  id: '.',
  exports: {},
  parent: null,
  filename: 'F:\\2020\\study\\Node.js\\node-course\\first-app\\app.js',
  loaded: false,
  children: [],
  paths:
   [ 'F:\\2020\\study\\Node.js\\node-course\\first-app\\node_modules',
     'F:\\2020\\study\\Node.js\\node-course\\node_modules',
     'F:\\2020\\study\\Node.js\\node_modules',
     'F:\\2020\\study\\node_modules',
     'F:\\2020\\node_modules',
     'F:\\node_modules' ] }
複製代碼

咱們看到這個屬性是一個空對象,全部添加到這個對象的屬性將可在外部訪問。

回到咱們的logger.js模塊,加入如下代碼

module.exports.log = log
複製代碼

咱們給exports對象添加一個log方法,讓它賦值爲咱們這裏的log函數,換句話說,咱們這裏的導出的對象,有一個同名的log方法,相似的咱們想公開這個url變量能夠這樣作

module.exports.url = url
複製代碼

咱們也能夠在導出的時候更名,不必定要相同的名字,好比在內部咱們叫它url,可是咱們在外部叫它endPoint

module.exports.endPoint = url
複製代碼

這個例子中,咱們不須要導出url變量,由於這純粹是實現的細節,在現實中,每一個模塊都有不少變量和函數,咱們只想公開最少限度的成員,由於咱們想保持模塊簡單易用。

打個比方,想象DVD播放器,DVD播放器有幾個按鈕是能夠給咱們操做的,這些按鈕就是咱們所說的DVD播放器的公開接口。

可是在播放器內部有不少複雜到咱們不須要了解的元件,它們徹底能夠從一個換成另一個,但無論怎麼換它們提供給外部的按鈕都是固定的。

logger模塊中,這個url是實現細節,其餘的模塊不須要了解它,它們只要調用log函數就能夠了,因此咱們讓log變爲公開的,而url保持私有。

接下來,咱們要在app模塊中使用它。

加載模塊

咱們使用require函數來加載模塊,這是Node纔有的函數,瀏覽器裏沒有,這個函數須要一個參數,也就是咱們想加載的模塊名稱。

require('./logger.js') // 這邊的.js能夠省略,由於Node知道這個是一個js文件,會自動添加擴展名
複製代碼

因爲app.jslogger.js在同一個文件夾下,咱們使用./來表明當前文件夾。

若是這個模塊是在子文件夾中能夠添加子文件夾的路徑。

require('./subFolder/logger')
複製代碼

若是在父文件夾,可使用../來表明相對路徑

require('../logger')
複製代碼

這個require函數返回參數模塊導出的對象,就是這邊的exports對象,咱們來代碼演示一下

app.js

var logger = require('./logger')
console.log(logger)
複製代碼

咱們把logger變量打印出來看下獲得什麼,回到控制檯,執行node app.js

{ log: [Function: log] }
複製代碼

咱們獲得一個對象,對象中有一個單一的函數log,咱們就能夠在app.js中調用這個函數了。

咱們來調用一個看看

var logger = require('./logger')

logger.log('message')
複製代碼

回到控制檯,運行程序,能夠看到打印了message信息。

這就是Node中模塊的工做方式,定義一個模塊,導出一個或多個成員。

做爲最佳實踐,導入的模塊應該保存在常量中,由於咱們有可能意外的將logger從新賦值。

var logger = require('./logger')

logger = 1 // 從新賦值

logger.log('message')
複製代碼

從新賦值爲1後,咱們再調用log方法,就會發生異常

TypeError: logger.log is not a function 複製代碼

做爲對比,咱們把它定義爲一個常量

const logger = require('./logger') // 使用const定義爲常量

logger = 1 // 從新賦值

logger.log('message')
複製代碼

再運行程序,獲得另外一個異常,試圖給常量賦值,有些專門檢查這類錯誤的工具,使用它們能夠避免在運行時出現問題,好比jshint

TypeError: Assignment to constant variable.
複製代碼

若是咱們意外的重寫了常量的值,咱們會在查錯時而不是運行時獲得報錯,這就是定義爲常量的好處。

若是咱們不想導出一個對象,只想導出一個簡單的函數,好比在logger模塊中,咱們不須要導出一個對象,咱們只有一個簡單的函數,對象在有多個屬性或方法時才須要用獲得。

咱們能夠將exports直接賦值爲log函數。

var url = 'http://mylogger.io/log'

function log(message) {
  console.log(message)
}

module.exports = log
複製代碼

這樣以後,咱們在app.jslogger就再也不是一個對象,它是一個咱們能夠直接調用的函數,咱們命名爲log會更好。

const log = require('./logger')

log('message') // 直接調用
複製代碼

因此在你的模塊中,你能夠導出對象或者單一的函數。

模塊包裝函數

咱們已經知道了Node模塊中定義的變量和函數的做用域只在當前模塊內,Node是如何實現的呢?

實際上,Node並無直接運行代碼,而是包裝在一個函數中,在運行時,咱們的代碼被轉換成這樣,咱們拿logger模塊來舉例。

(function (exports, require, module, __filename, __dirname) {
    var url = 'http://mylogger.io/log'

    function log(message) {
      console.log(message)
    }
    
    module.exports = log
})
複製代碼

若是你是一個有經驗的JavaScript開發者,你可能知道這是當即調用函數表達式,也叫作IIFE。若是你不清楚也沒關係,這並非Node的內容,這邊想表達的是Node不直接執行代碼,Node老是將代碼包裹在這樣的一個函數中。

看看這個函數的參數,看下require,這個require看起來像全局的但實際不是,事實上它是每一個模塊本地的,在每一個模塊中,require都是做爲參數傳給函數,咱們稱之爲模塊包裝函數

還有module參數,還有module.exports簡寫爲exports,因此當你想將函數公開的時候能夠這麼寫

module.exports.log = log
複製代碼

也能夠這麼寫

exports.log = log
複製代碼

可是若是沒有module對象引用就不能重置exports對象,換句話說,不能給exports對象賦值。

exports = log // 不要這麼寫
複製代碼

由於這個exportsmodule.exports的一個引用,你不能更改它的引用。

還有__filename__dirname分別表明文件名和目錄名,咱們打印出來看一下。

console.log(__filename)
console.log(__dirname)

var url = 'http://mylogger.io/log'

function log(message) {
  console.log(message)
}

module.exports = log
複製代碼

回到控制檯,運行程序,打印結果以下

F:\2020\study\Node.js\node-course\first-app\logger.js
F:\2020\study\Node.js\node-course\first-app
message
複製代碼

第一個是文件名,第二個是文件的完整路徑。

如今,咱們對Node模塊和它的運做方式有個大概的印象了,咱們知道了如何建立它們和加載它們。

Node包含了不少有用和經常使用的模塊,涉及的知識點較多,下篇文章咱們再一塊兒來學習下。

最後

感謝您的閱讀,但願對你有所幫助。因爲本人水平有限,若是文中有描述不當的地方,煩請指正,很是感謝。

關注

歡迎你們關注個人公衆號前端幫幫忙

下篇文章預告

Node的模塊系統(路徑模塊、操做系統模塊、文件模塊、事件模塊、HTTP模塊)

相關文章
相關標籤/搜索