1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        請說說ES6 module和CommonJS的區(qū)別

        共 10753字,需瀏覽 22分鐘

         ·

        2021-02-27 23:22

        面試常問系列。

        老規(guī)矩先看目錄:


        CommonJS:


        1.module代表當前模塊:


        CommonJS中,一個文件就是一個模塊,模塊中的變量,函數(shù),類都是私有的外部不可以訪問,并規(guī)定module代表當前模塊,exports是對外的接口。

        CommonJS主要依賴于module這個類,我們可以看一下module上面的相關(guān)屬性:

        Module {
          id'.'// 如果是 mainModule id 固定為 '.',如果不是則為模塊絕對路徑
          exports: {}, // 模塊最終 exports
          filename'/absolute/path/to/entry.js'// 當前模塊的絕對路徑
          loadedfalse// 模塊是否已加載完畢
          children: [], // 被該模塊引用的模塊
          parent''// 第一個引用該模塊的模塊
          paths: [ // 模塊的搜索路徑
           '/absolute/path/to/node_modules',
           '/absolute/path/node_modules',
           '/absolute/node_modules',
           '/node_modules'
          ]
        }


        2.為什么可以直接使用exports,module,__dirname這些方法屬性?


        要回答這個問題我們要從CommonJS內(nèi)部執(zhí)行代碼的原理說起。

        CommonJS規(guī)范中代碼在運行時會被包裹在一個立即執(zhí)行函數(shù)中,之后我們會改變這個立即執(zhí)行函數(shù)中內(nèi)部this的指向,指向的便是module.exports這個空對象。這便可以很好的解釋我們node.js中內(nèi)部this指向的是一個空對象的問題。

        邏輯代碼:

        (function (exports, require, module, __filename, __dirname{
            let name = "lm";
            exports.name = name;
        });
        jsScript.call(module.exports, args);


        之后我們會給其傳遞exports, require, module,,__filename等參數(shù),所以我們可以在直接在編寫node.js代碼中使用這些變量。


        3.exports與module.exports有什么區(qū)別?


        在node.js中我們導出一個變量,函數(shù),或者類一般有兩種導出方法:

        function A({
            console.log('過年好!');
        }

        // 法一:module.exports.A = A;
        // 法二:exports.A = A;


        這兩種方法有什么區(qū)別嗎?其實exports只是module.exports的引用罷了,所以實際上這兩種方法在使用上的效果是一樣的。

        const module = {
            'exports': {
            }
        }

        const exports = module.exports;
        exports.name = 'Andy'//完全等價于 module.exports.name = 'Andy';


        所以當我們使用exports或者module.exports導出模塊時,其實也就是給module.exports這個對象添加屬性,之后我們使用require引入模塊時得到的便是module.exports這個對象。


        注意:既然是對象屬性的引用,所以當我們使用一個模塊中的方法修改該模塊中的變量,之后導出的變量的結(jié)果是不變的,也就是說只要一個變量已經(jīng)被導出了之后在模塊內(nèi)部對變量的修改都將無意義,這個情況要格外注意。(這點與ES6 module有很大的不同)

        a.js:

        let count = 1;

        function add({
            count += 1;
        }

        exports.count = count;
        exports.add = add;

        b.js:

        let Module = require('./a');

        console.log(Module.count); // 1
        Module.add();
        console.log(Module.count); // 1


        4.模塊引入后自動緩存


        我們在使用require時可能是這樣的:

        let Module = require('./a');


        如果是系統(tǒng)模塊,或者第三方模塊我們可以直接寫模塊名:

        let fs = require('fs');


        但實際上在require模塊時我們都是要根據(jù)計算機中的絕對地址來引入,這個根據(jù)相對地址或者包名來查找文件的過程是比較消耗時間的,我們可以通過module.paths來打印一下查找的過程:

        [
          'c:\\Users\\dell\\Desktop\\web-design\\前端相關(guān)題目\\字節(jié)跳動\\node_modules',
          'c:\\Users\\dell\\Desktop\\web-design\\前端相關(guān)題目\\node_modules',
          'c:\\Users\\dell\\Desktop\\web-design\\node_modules',
          'c:\\Users\\dell\\Desktop\\node_modules',
          'c:\\Users\\dell\\node_modules',
          'c:\\Users\\node_modules',
          'c:\\node_modules'
        ]


        所以為了提高性能,我們每次在文件中引入一個模塊時,我們都會將引入的這個模塊與其相應(yīng)的絕對地址進行緩存,如果在一個文件中多次引入相同的模塊這個模塊只會被加載一次。

        我們可以使用require.cache打印出當前模塊的依賴模塊看看,我們可以發(fā)現(xiàn)其是以絕對地址為key,模塊為value的對象:

        [Object: null prototype] {
          'c:\\Users\\dell\\Desktop\\web-design\\前端相關(guān)題目\\字節(jié)跳動\\b.js': Module {
            id: '.',
            path: 'c:\\Users\\dell\\Desktop\\web-design\\前端相關(guān)題目\\字節(jié)跳動',
            exports: {},
            parent: null,
            filename: 'c:\\Users\\dell\\Desktop\\web-design\\前端相關(guān)題目\\字節(jié)跳動\\b.js',
            loaded: false,
            children: [ [Module] ],
            paths: [
              'c:\\Users\\dell\\Desktop\\web-design\\前端相關(guān)題目\\字節(jié)跳動\\node_modules',
              'c:\\Users\\dell\\Desktop\\web-design\\前端相關(guān)題目\\node_modules',
              'c:\\Users\\dell\\Desktop\\web-design\\node_modules',
              'c:\\Users\\dell\\Desktop\\node_modules',
              'c:\\Users\\dell\\node_modules',
              'c:\\Users\\node_modules',
              'c:\\node_modules'
            ]
          },
          'c:\\Users\\dell\\Desktop\\web-design\\前端相關(guān)題目\\字節(jié)跳動\\a.js': Module {
            id: 'c:\\Users\\dell\\Desktop\\web-design\\前端相關(guān)題目\\字節(jié)跳動\\a.js',
            path: 'c:\\Users\\dell\\Desktop\\web-design\\前端相關(guān)題目\\字節(jié)跳動',
            exports: { count: 1, add: [Function: add] },
            parent: Module {
              id: '.',
              path: 'c:\\Users\\dell\\Desktop\\web-design\\前端相關(guān)題目\\字節(jié)跳動',
              exports: {},
              parent: null,
              filename: 'c:\\Users\\dell\\Desktop\\web-design\\前端相關(guān)題目\\字節(jié)跳動\\b.js',
              loaded: false,
              children: [Array],
              paths: [Array]
            },
            filename: 'c:\\Users\\dell\\Desktop\\web-design\\前端相關(guān)題目\\字節(jié)跳動\\a.js',
            loaded: true,
            children: [],
            paths: [
              'c:\\Users\\dell\\Desktop\\web-design\\前端相關(guān)題目\\字節(jié)跳動\\node_modules',
              'c:\\Users\\dell\\Desktop\\web-design\\前端相關(guān)題目\\node_modules',
              'c:\\Users\\dell\\Desktop\\web-design\\node_modules',
              'c:\\Users\\dell\\Desktop\\node_modules',
              'c:\\Users\\dell\\node_modules',
              'c:\\Users\\node_modules',
              'c:\\node_modules'
            ]
          }
        }


        從而可以很好的解釋這個例子:

        // a.js
        module.exports = {
            foo1,
        };

        // main.js
        const a1 = require('./a.js');
        a1.foo = 2;

        const a2 = require('./a.js');

        console.log(a2.foo); // 2
        console.log(a1 === a2); // true


        我們可以理解為只要模塊一引入加載完,即使再次引入也還是之前的模塊。


        同時緩存還很好的解決了循環(huán)引用的問題:舉個例子,現(xiàn)在有模塊 a require 模塊 b;而模塊 b 又 require 了模塊 a。

        // main.js
        const a = require('./a');
        console.log('in main, a.a1 = %j, a.a2 = %j', a.a1, a.a2);


        // a.js
        exports.a1 = true;
        const b = require('./b.js');
        console.log('in a, b.done = %j', b.done);


        exports.a2 = true;
        // b.js
        const a = require('./a.js');
        console.log('in b, a.a1 = %j, a.a2 = %j', a.a1, a.a2);
        復制代碼


        程序執(zhí)行結(jié)果如下:
        in b, a.a1 = true, a.a2 = undefined
        in main, a.a1 = true, a.a2 = true
        復制代碼


        實際上在模塊 a 代碼執(zhí)行之前就已經(jīng)創(chuàng)建了 Module 實例寫入了緩存,此時代碼還沒執(zhí)行,exports 是個空對象。

        '/Users/evan/Desktop/module/a.js'
           Module {
             exports: {},
             //...
          }
        }


        代碼 exports.a1 = true; 修改了 module.exports 上的 a1true, 這時候 a2 代碼還沒執(zhí)行。

        '/Users/evan/Desktop/module/a.js'
           Module {
             exports: {
              a1true
            }
             //...
          }
        }


        進入 b 模塊,require a.js 時發(fā)現(xiàn)緩存上已經(jīng)存在了,獲取 a 模塊上的 exports 。打印 a1, a2 分別是 true,和 undefined。

        運行完 b 模塊,繼續(xù)執(zhí)行 a 模塊剩余的代碼,exports.a2 = true; 又往 exports 對象上增加了 a2 屬性,此時 module aexport 對象 a1, a2 均為 true。

        exports: { 
          a1true,
          a2true
        }


        再回到 main 模塊,由于 require('./a.js') 得到的是 module a export 對象的引用,這時候打印 a1, a2 就都為 true。


        這里還有一個需要注意的點就是,模塊在加載時是同步阻塞的,只有引入的模塊加載完才會執(zhí)行后面的語句,大家記住就好。



        5.總結(jié):

        說了這么多我們主要的目的還是為了面試,所以這里小小的總結(jié)一下:

        • 在CommonJS中一個文件就是一個模塊,模塊中的變量,方法,類都是私有的
        • module代表當前模塊,module.exports代表模塊對外的接口
        • 模塊在加載時所有內(nèi)容會被放在一個立即執(zhí)行函數(shù)中,函數(shù)的this指向module.exports這個空對象,而exports只是module.exports的引用而已
        • 加載模塊是同步阻塞的,加載后會進行緩存,多次引入只會加載一次
        • require得到的模塊中變量,方法,類的拷貝,并不是直接的引用


        ES6 module:


        這個是我們最常用的,我們通常會在Vue或者Webpack中來使用,其并不像是CommonJS那樣將代碼放在一個立即執(zhí)行函數(shù)中(依靠閉包)從而完成模塊化,而是從語法層面完成的模塊化。一般情況下我們寫的ES6 module語法會還是會通過bable或者Webpack等工具轉(zhuǎn)化為CommonJS語法的。

        對于ES6 module就不詳細介紹其實現(xiàn)原理了,主要想說一下其特點并且和CommonJS相比有區(qū)別來方便大家記憶。


        1.在執(zhí)行模塊前會先加載所有的依賴模塊


        這點也是最重要的一點,通過上面我們知道CommonJS是在執(zhí)行到需要加載依賴模塊時,會(同步阻塞)停下當前任務(wù)去加載相應(yīng)的依賴模塊,而對于ES module來說無論你在哪一行引用依賴模塊,其都會在一開始就進行加載相應(yīng)的依賴模塊。

        // a.mjs
        export const a1 = true;
        import * as b from './b.mjs';
        export const a2 = true;

        // b.mjs
        import { a1, a2 } from './a.mjs'
        console.log(a1, a2);


        在這種情況下,如果是之前的CommonJS會輸出true與undefined,而現(xiàn)在會直接報錯:ReferenceError: Cannot access 'a1' before initialization。

        同樣的原因我們在CommonJS中可以這樣寫,而在ES module中會報錯:

        require(path.join('xxxx''xxx.js'))


         

        同樣如果我們在CommonJS中引入一個沒有exports的變量那么在代碼執(zhí)行時才會報錯,而ES module在剛開始就會報錯。


        2.import的是變量的引用

        在CommonJS的情況下:

        // counter.js
        let count = 1;

        function increment ({
          count++;
        }

        module.exports = {
          count,
          increment
        }

        // main.js
        const counter = require('counter.cjs');

        counter.increment();
        console.log(counter.count); // 1


        在ES module情況下:

        // counter.mjs
        export let count = 1;

        export function increment ({
          count++;
        }

        // main.mjs
        import { increment, count } from './counter.mjs'

        increment();
        console.log(count); // 2


        這一次我們導入是變量的引用了,這樣可以避免之前CommonJS在實際開發(fā)中的很多問題,實際類似于這樣。

        exports.counter = 1;

        exports.increment = function ({
            exports.counter++;
        }


        3.ES module是部分導入


        這個很好理解,在CommonJS中我們加載一個模塊需要將該模塊的所有接口導入進來,而ES6 module里我們可以按需只導入我們想要的接口。


        最后順便再提一點:出于兼容性考慮對于像Webpack我們在使用的ES module時最終還是會轉(zhuǎn)換為CommonJS規(guī)范,所以有些時候我們使用require時導入的并不是目標值,我們往往需要加一個.defult才行,這就是因為ES moduleexports defult語法所造成的。


        4.總結(jié):


        其實ES6 module相對于CommonJS最大的區(qū)別就是兩點:

        • 在執(zhí)行模塊前首先需要加載所有的依賴模塊,如果加載有問題直接報錯
        • ES6 module的模塊引入的都是變量,函數(shù),類的引用這是很有先進性的


        還有值得一提的就是ES6 module可以按需引入自己需要的接口,兩者也是具有相同點的就是都會對已經(jīng)引入的模塊進行緩存,如果多次引入只會執(zhí)行一次


        參考:


        FESKY:CommonJS 和 ES6 Module 究竟有什么區(qū)別?[1]
        雨中前行:再次梳理AMD、CMD、CommonJS、ES6 Module的區(qū)別[2]

        參考

        [1]

        CommonJS 和 ES6 Module 究竟有什么區(qū)別?: https://juejin.cn/post/6844904080955932680#heading-7

        [2]

        再次梳理AMD、CMD、CommonJS、ES6 Module的區(qū)別: https://juejin.cn/post/6844903983987834888#heading-10


        瀏覽 68
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            午夜一级免费电影 | 大香蕉伊人网视频 | 大美女swag | 香港三日三级少妇三级66 | 厨房里的放荡h | 欧美激情网页 | 色88久久久久高潮综合影院 | 一级A片一毛片大全 | 成年人免费性爱视频 | 亚欧洲精品视频 |