掃二維碼與項(xiàng)目經(jīng)理溝通
我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
前言

10年積累的成都做網(wǎng)站、成都網(wǎng)站制作經(jīng)驗(yàn),可以快速應(yīng)對(duì)客戶對(duì)網(wǎng)站的新想法和需求。提供各種問題對(duì)應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識(shí)你,你也不認(rèn)識(shí)我。但先網(wǎng)站制作后付款的網(wǎng)站建設(shè)流程,更有興安盟烏蘭浩特免費(fèi)網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。
本文將為大家透徹的介紹關(guān)于Node的模塊化——CommonJS的一切。
看完本文可以掌握,以下幾個(gè)方面:
一.什么是模塊化?
在很多開發(fā)的情況下,我們都知道要使用模塊化開發(fā),那為什么要使用它呢?
而事實(shí)上,模塊化開發(fā)最終的目的是將程序劃分成一個(gè)個(gè)小的結(jié)構(gòu);
上面說提到的結(jié)構(gòu),就是模塊;
按照這種結(jié)構(gòu)劃分開發(fā)程序的過程,就是模塊化開發(fā)的過程;
二.JavaScript設(shè)計(jì)缺陷
在網(wǎng)頁開發(fā)的早期,由于JavaScript僅僅作為一種腳本語言,只能做一些簡單的表單驗(yàn)證或動(dòng)畫實(shí)現(xiàn)等,它還是具有很多的缺陷問題的,比如:
但隨著前端和JavaScript的快速發(fā)展,JavaScript代碼變得越來越復(fù)雜了;
所以,模塊化已經(jīng)是JavaScript一個(gè)非常迫切的需求:
到此,我們明白了為什么要用模塊化開發(fā)?
那如果沒有模塊化會(huì)帶來什么問題呢?
三.沒有模塊化的問題
當(dāng)我們?cè)诠久鎸?duì)一個(gè)大型的前端項(xiàng)目時(shí),通常是多人開發(fā)的,會(huì)把不同的業(yè)務(wù)邏輯分步在多個(gè)文件夾當(dāng)中。
2.1 沒有模塊化給項(xiàng)目帶來的弊端
假設(shè)有兩個(gè)人,分別是小豪和小紅在開發(fā)一個(gè)項(xiàng)目
項(xiàng)目的目錄結(jié)構(gòu)是這樣的
小豪開發(fā)的bar.js文件
- var name = "小豪";
- console.log("bar.js----", name);
小豪開發(fā)的baz.js文件
- console.log("baz.js----", name);
小紅開發(fā)的foo.js文件
- var name = "小紅";
- console.log("foo.js----", name);
引用路徑如下:
最后當(dāng)我去執(zhí)行的時(shí)候,卻發(fā)現(xiàn)執(zhí)行結(jié)果:
當(dāng)我們看到這個(gè)結(jié)果,有的小伙伴可能就會(huì)驚訝,baz.js文件不是小豪寫的么?為什么會(huì)輸出小紅的名字呢?
究其原因,我們才發(fā)現(xiàn),其實(shí)JavaScript是沒有模塊化的概念(至少到現(xiàn)在為止還沒有用到ES6規(guī)范),換句話說就是每個(gè).js文件并不是一個(gè)獨(dú)立的模塊,沒有自己的作用域,所以在.js文件中定義的變量,都是可以被其他的地方共享的,所以小豪開發(fā)的baz.js里面的name,其實(shí)訪問的是小紅重新聲明的。
但是共享也有一點(diǎn)不好就是,項(xiàng)目的其他協(xié)作人員也可以隨意的改變它們,顯然這不是我們想要的。
2.2 IIFE解決早期的模塊化問題
所以,隨著前端的發(fā)展,模塊化變得必不可少,那么在早期是如何解決的呢?
在早期,因?yàn)楹瘮?shù)是有自己的作用域,所以可以采用立即函數(shù)調(diào)用表達(dá)式(IIFE),也就是自執(zhí)行函數(shù),把要供外界使用的變量作為函數(shù)的返回結(jié)果。
小豪——bar.js
- var moduleBar = (function () {
- var name = "小豪";
- var age = "18";
- console.log("bar.js----", name, age);
- return {
- name,
- age,
- };
- })();
小豪——baz.js
- console.log("baz.js----", moduleBar.name);
- console.log("baz.js----", moduleBar.age);
小紅——foo.js
- (function () {
- var name = "小紅";
- var age = 20;
- console.log("foo.js----", name, age);
- })();
來看一下,解決之后的輸出結(jié)果,原調(diào)用順序不變;
但是,這又帶來了新的問題:
所以現(xiàn)在急需一個(gè)統(tǒng)一的規(guī)范,來解決這些缺陷問題,就此CommonJS規(guī)范問世了。
三.Node模塊化開發(fā)——CommonJS規(guī)范
3.1 CommonJS規(guī)范特性
CommonJS是一個(gè)規(guī)范,最初提出來是在瀏覽器以外的地方使用,并且當(dāng)時(shí)被命名為ServerJS,后來為了體現(xiàn)它的廣泛性,修改為CommonJS規(guī)范。
正是因?yàn)镹ode中對(duì)CommonJS進(jìn)行了支持和實(shí)現(xiàn),所以它具備以下幾個(gè)特點(diǎn);
無疑,模塊化的核心是導(dǎo)出和導(dǎo)入,Node中對(duì)其進(jìn)行了實(shí)現(xiàn):
3.2 CommonJS配合Node模塊化開發(fā)假設(shè)現(xiàn)在有兩個(gè)文件:
bar.js
- const name = "時(shí)光屋小豪";
- const age = 18;
- function sayHello(name) {
- console.log("hello" + name);
- }
main.js
- console.log(name);
- console.log(age);
執(zhí)行node main.js之后,會(huì)看到
這是因?yàn)樵诋?dāng)前main.js模塊內(nèi),沒有發(fā)現(xiàn)name這個(gè)變量;
這點(diǎn)與我們前面看到的明顯不同,因?yàn)镹ode中每個(gè)js文件都是一個(gè)單獨(dú)的模塊。
那么如果要在別的文件內(nèi)訪問bar.js變量
3.3 exports導(dǎo)出
exports是一個(gè)對(duì)象,我們可以在這個(gè)對(duì)象中添加很多個(gè)屬性,添加的屬性會(huì)導(dǎo)出。
bar.js文件導(dǎo)出:
- const name = "時(shí)光屋小豪";
- const age = 18;
- function sayHello(name) {
- console.log("hello" + name);
- }
- exports.name = name;
- exports.age = age;
- exports.sayHello = sayHello;
main.js文件導(dǎo)入:
- const bar = require('./bar');
- console.log(bar.name); // 時(shí)光屋小豪
- console.log(bar.age); // 18
其中要注意的點(diǎn):
main.js中的bar變量等于exports對(duì)象;
- bar = exports
3.4 從內(nèi)存角度分析bar和exports是同一個(gè)對(duì)象
在Node中,有一個(gè)特殊的全局對(duì)象,其實(shí)exports就是其中之一。
如果在文件內(nèi),不再使用exports.xxx的形式導(dǎo)出某個(gè)變量的話,其實(shí)exports就是一個(gè)空對(duì)象。
模塊之間的引用關(guān)系
為了進(jìn)一步論證,bar和exports是同一個(gè)對(duì)象:
我們加入定時(shí)器看看
所以綜上所述,Node中實(shí)現(xiàn)CommonJS規(guī)范的本質(zhì)就是對(duì)象的引用賦值(淺拷貝本質(zhì))。
把exports對(duì)象的引用賦值bar對(duì)象上。
3.5 module.exports又是什么?
但是Node中我們經(jīng)常使用module.exports導(dǎo)出東西,也會(huì)遇到這樣的面試題:
module.exports和exports有什么關(guān)系或者區(qū)別呢?
3.6 require細(xì)節(jié)
require本質(zhì)就是一個(gè)函數(shù),可以幫助我們引入一個(gè)文件(模塊)中導(dǎo)入的對(duì)象。
require的查找規(guī)則https://nodejs.org/dist/latest-v14.x/docs/api/modules.html#modules_all_together
3.7 require模塊的加載順序
結(jié)論一:模塊在被第一次引入時(shí),模塊中的js代碼會(huì)被運(yùn)行一次
- // aaa.js
- const name = 'coderwhy';
- console.log("Hello aaa");
- setTimeout(() => {
- console.log("setTimeout");
- }, 1000);
- // main.js
- const aaa = require('./aaa');
aaa.js中的代碼在引入時(shí)會(huì)被運(yùn)行一次
結(jié)論二:模塊被多次引入時(shí),會(huì)緩存,最終只加載(運(yùn)行)一次
- // main.js
- const aaa = require('./aaa');
- const bbb = require('./bbb');
- /// aaa.js
- const ccc = require("./ccc");
- // bbb.js
- const ccc = require("./ccc");
- // ccc.js
- console.log('ccc被加載');
ccc中的代碼只會(huì)運(yùn)行一次。
為什么只會(huì)加載運(yùn)行一次呢?
結(jié)論三:如果有循環(huán)引入,那么加載順序是什么?
如果出現(xiàn)下面模塊的引用關(guān)系,那么加載順序是什么呢?
多個(gè)模塊的引入關(guān)系
四.module.exports
4.1 真正導(dǎo)出的是module.exports
以下是通過維基百科對(duì)CommonJS規(guī)范的解析:
但是,為什么exports也可以導(dǎo)出呢?
4.2 module.exports和exports有什么關(guān)系或者區(qū)別呢?
聯(lián)系:module.exports = exports
進(jìn)一步論證module.exports = exports
- // bar.js
- const name = "時(shí)光屋小豪";
- exports.name = name;
- setTimeout(() => {
- module.exports.name = "哈哈哈";
- console.log("bar.js中1s之后", exports.name);
- }, 1000);
- // main.js
- const bar = require("./bar");
- console.log("main.js", bar.name);
- setTimeout((_) => {
- console.log("main.js中1s之后", bar.name);
- }, 2000);
在上面代碼中,只要在bar.js中修改exports對(duì)象里的屬性,導(dǎo)出的結(jié)果都會(huì)變,因?yàn)榧词拐嬲龑?dǎo)出的是 module.exports,而module.exports和exports是都是相同的引用地址,改變了其中一個(gè)的屬性,另一個(gè)也會(huì)跟著改變。
注意:真正導(dǎo)出的模塊內(nèi)容的核心其實(shí)是module.exports,只是為了實(shí)現(xiàn)CommonJS的規(guī)范,剛好module.exports對(duì)exports對(duì)象使用的是同一個(gè)引用而已
區(qū)別:有以下兩點(diǎn)
那么如果,代碼這樣修改了:
圖解module.exports和exports的區(qū)別
講完它們兩個(gè)的區(qū)別,來看下面這兩個(gè)例子,看看自己是否真正掌握了module.exports的用法
4.3 關(guān)于module.exports的練習(xí)題
練習(xí)1:導(dǎo)出的變量為值類型
- // bar.js
- let name = "時(shí)光屋小豪";
- setTimeout(() => {
- name = "123123";
- }, 1000);
- module.exports = {
- name: name,
- age: "20",
- sayHello: function (name) {
- console.log("你好" + name);
- },
- };
- // main.js
- const bar = require("./bar");
- console.log("main.js", bar.name); // main.js 時(shí)光屋小豪
- setTimeout(() => {
- console.log("main.js中2s后", bar.name); // main.js中2s后 時(shí)光屋小豪
- }, 2000);
練習(xí)2:導(dǎo)出的變量為引用類型
- // bar.js
- let info = {
- name: "時(shí)光屋小豪",
- };
- setTimeout(() => {
- info.name = "123123";
- }, 1000);
- module.exports = {
- info: info,
- age: "20",
- sayHello: function (name) {
- console.log("你好" + name);
- },
- };
- // main.js
- const bar = require("./bar");
- console.log("main.js", bar.info.name); // main.js 時(shí)光屋小豪
- setTimeout(() => {
- console.log("main.js中2s后", bar.info.name); // main.js中2s后 123123
- }, 2000);
從main.js輸出結(jié)果來看,定時(shí)器修改的name變量的結(jié)果,并沒有影響main.js中導(dǎo)入的結(jié)果。
五.CommonJS的加載過程
CommonJS模塊加載js文件的過程是運(yùn)行時(shí)加載的,并且是同步的:
- const flag = true;
- if (flag) {
- const foo = require('./foo');
- console.log("等require函數(shù)執(zhí)行完畢后,再輸出這句代碼");
- }
CommonJS通過module.exports導(dǎo)出的是一個(gè)對(duì)象:
六.CommonJS規(guī)范的本質(zhì)
CommonJS規(guī)范的本質(zhì)就是對(duì)象的引用賦值
后續(xù)文章
《JavaScript模塊化——ES Module》
在下一篇文章中,
【編輯推薦】

我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流