欧美一区二区三区老妇人-欧美做爰猛烈大尺度电-99久久夜色精品国产亚洲a-亚洲福利视频一区二区

node模塊相關(guān)的面試題及答案有哪些

本文小編為大家詳細(xì)介紹“node模塊相關(guān)的面試題及答案有哪些”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“node模塊相關(guān)的面試題及答案有哪些”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學(xué)習(xí)新知識吧。

創(chuàng)新互聯(lián):自2013年創(chuàng)立以來為各行業(yè)開拓出企業(yè)自己的“網(wǎng)站建設(shè)”服務(wù),為近千家公司企業(yè)提供了專業(yè)的網(wǎng)站制作、做網(wǎng)站、網(wǎng)頁設(shè)計和網(wǎng)站推廣服務(wù), 按需制作網(wǎng)站由設(shè)計師親自精心設(shè)計,設(shè)計的效果完全按照客戶的要求,并適當(dāng)?shù)奶岢龊侠淼慕ㄗh,擁有的視覺效果,策劃師分析客戶的同行競爭對手,根據(jù)客戶的實際情況給出合理的網(wǎng)站構(gòu)架,制作客戶同行業(yè)具有領(lǐng)先地位的。

熱更新

如何在不重啟 node 進(jìn)程的情況下熱更新一個 js/json 文件? 這個問題本身是否有問題?

簡單的說就是,require A模塊之后,會把A模塊放入到緩存里,第二次取的時候就取緩存了,所以你僅僅改變了文件并不會讓這個文件重新加載,所以我們就需要把緩存去掉,讓這個文件能重新加載。

簡單地說父模塊 A 引入子模塊 B 的步驟如下:

  • 判斷子模塊 B 緩存是否存在

  • 如果不存在則對 B 進(jìn)行編譯解析

    • 添加 B 模塊緩存至 require.cache(其中 key 為模塊 B 的全路徑)

    • 添加 B 模塊引用至父模塊 A 的 children 數(shù)組中

  • 如果存在,判斷父模塊 A 的 children 數(shù)組中是否存在 B,如不存在則添加 B 模塊引用。

所以在node.js做熱更新是十分麻煩的,一些庫做的也不夠好。這個問題的終極解決方案是借助一些第三方工具,例如k8s,k8s可以輕松實現(xiàn)滾動升級,也就是如果要做熱更新,k8s會把新的服務(wù)起起來,然后把流量切換到新的服務(wù)上(pod上),然后老服務(wù)再關(guān)閉。

不過熱更新 json 之類的配置文件的話, 還是可以簡單的實現(xiàn)的, 可以直接存到后端的數(shù)據(jù)庫里,這樣就避免node.js的緩存問題了。

模塊機(jī)制

1.1 請介紹一下node里的模塊是什么

Node中,每個文件模塊都是一個對象,它的定義如下:

function Module(id, parent) {
  this.id = id;
  this.exports = {};
  this.parent = parent;
  this.filename = null;
  this.loaded = false;
  this.children = [];
}

module.exports = Module;
var module = new Module(filename, parent);

所有的模塊都是 Module 的實例??梢钥吹剑?dāng)前模塊(module.js)也是 Module 的一個實例。

1.2 請介紹一下require的模塊加載機(jī)制

這道題基本上就可以了解到面試者對Node模塊機(jī)制的了解程度 基本上面試提到

1、先計算模塊路徑 2、如果模塊在緩存里面,取出緩存 3、加載模塊 4、的輸出模塊的exports屬性即可

// require 其實內(nèi)部調(diào)用 Module._load 方法
Module._load = function(request, parent, isMain) {
  //  計算絕對路徑
  var filename = Module._resolveFilename(request, parent);

  //  第一步:如果有緩存,取出緩存
  var cachedModule = Module._cache[filename];
  if (cachedModule) {
    return cachedModule.exports;

  // 第二步:是否為內(nèi)置模塊
  if (NativeModule.exists(filename)) {
    return NativeModule.require(filename);
  }
  
  /********************************這里注意了**************************/
  // 第三步:生成模塊實例,存入緩存
  // 這里的Module就是我們上面的1.1定義的Module
  var module = new Module(filename, parent);
  Module._cache[filename] = module;

  /********************************這里注意了**************************/
  // 第四步:加載模塊
  // 下面的module.load實際上是Module原型上有一個方法叫Module.prototype.load
  try {
    module.load(filename);
    hadException = false;
  } finally {
    if (hadException) {
      delete Module._cache[filename];
    }
  }

  // 第五步:輸出模塊的exports屬性
  return module.exports;
};

接著上一題繼續(xù)發(fā)問

1.3 加載模塊時,為什么每個模塊都有__dirname,__filename屬性呢,new Module的時候我們看到1.1部分沒有這兩個屬性的,那么這兩個屬性是從哪里來的

// 上面(1.2部分)的第四步module.load(filename)
// 這一步,module模塊相當(dāng)于被包裝了,包裝形式如下
// 加載js模塊,相當(dāng)于下面的代碼(加載node模塊和json模塊邏輯不一樣)
(function (exports, require, module, __filename, __dirname) {
  // 模塊源碼
  // 假如模塊代碼如下
  var math = require('math');
  exports.area = function(radius){
      return Math.PI * radius * radius
  }
});

也就是說,每個module里面都會傳入__filename, __dirname參數(shù),這兩個參數(shù)并不是module本身就有的,是外界傳入的

1.4 我們知道node導(dǎo)出模塊有兩種方式,一種是exports.xxx=xxx和Module.exports={}有什么區(qū)別嗎

exports其實就是module.exports 其實1.3問題的代碼已經(jīng)說明問題了,接著我引用廖雪峰大神的講解,希望能講的更清楚

module.exports vs exports

很多時候,你會看到,在Node環(huán)境中,有兩種方法可以在一個模塊中輸出變量:

方法一:對module.exports賦值:

// hello.js

function hello() {
    console.log('Hello, world!');
}

function greet(name) {
    console.log('Hello, ' + name + '!');
}

module.exports = {
    hello: hello,
    greet: greet
};

方法二:直接使用exports:

// hello.js

function hello() {
    console.log('Hello, world!');
}

function greet(name) {
    console.log('Hello, ' + name + '!');
}

function hello() {
    console.log('Hello, world!');
}

exports.hello = hello;
exports.greet = greet;

但是你不可以直接對exports賦值:

// 代碼可以執(zhí)行,但是模塊并沒有輸出任何變量:
exports = {
    hello: hello,
    greet: greet
};

如果你對上面的寫法感到十分困惑,不要著急,我們來分析Node的加載機(jī)制:

首先,Node會把整個待加載的hello.js文件放入一個包裝函數(shù)load中執(zhí)行。在執(zhí)行這個load()函數(shù)前,Node準(zhǔn)備好了module變量:

var module = {
    id: 'hello',
    exports: {}
};
load()函數(shù)最終返回module.exports:

var load = function (exports, module) {
    // hello.js的文件內(nèi)容
    ...
    // load函數(shù)返回:
    return module.exports;
};

var exportes = load(module.exports, module);

也就是說,默認(rèn)情況下,Node準(zhǔn)備的exports變量和module.exports變量實際上是同一個變量,并且初始化為空對象{},于是,我們可以寫:

exports.foo = function () { return 'foo'; };
exports.bar = function () { return 'bar'; };

也可以寫:

module.exports.foo = function () { return 'foo'; };
module.exports.bar = function () { return 'bar'; };

換句話說,Node默認(rèn)給你準(zhǔn)備了一個空對象{},這樣你可以直接往里面加?xùn)|西。

但是,如果我們要輸出的是一個函數(shù)或數(shù)組,那么,只能給module.exports賦值:

module.exports = function () { return 'foo'; };

給exports賦值是無效的,因為賦值后,module.exports仍然是空對象{}。

結(jié)論 如果要輸出一個鍵值對象{},可以利用exports這個已存在的空對象{},并繼續(xù)在上面添加新的鍵值;

如果要輸出一個函數(shù)或數(shù)組,必須直接對module.exports對象賦值。

所以我們可以得出結(jié)論:直接對module.exports賦值,可以應(yīng)對任何情況:

module.exports = {
    foo: function () { return 'foo'; }
};

或者:

module.exports = function () { return 'foo'; };

最終,我們強(qiáng)烈建議使用module.exports = xxx的方式來輸出模塊變量,這樣,你只需要記憶一種方法。

上下文 Vm模塊

通過上面的問題,面試官又拋出一個問題,每個require的js文件,作用域如何保證獨立呢?

其實每一個require的js文件,本身就是一個字符串, 文件是不是字符串嘛,所以我們需要一種機(jī)制能夠把字符串編譯為可以運行的javascript語言。

實際上從上面的討論我們知道,require會把引入的js包裹在function中,所以它的作用域天然就是獨立的。

接著講本章的vm模塊,vm模塊和function都可以建立自己獨立的作用域,并且vm、function、eval還可以把字符串當(dāng)做目標(biāo)代碼執(zhí)行。所以這三者的區(qū)別就需要面試者了解。

  • eval

  • Function

  • vm

eval、Function,在執(zhí)行目標(biāo)代碼時,會有一個最大的問題就是安全性,無論如何目標(biāo)代碼不能影響我正常的服務(wù),也就是說,這個執(zhí)行環(huán)境得是一個沙盒環(huán)境,而eval顯然并不具備這個能力。如果需要一段不信任的代碼放任它執(zhí)行,那么不光服務(wù),整個服務(wù)器的文件系統(tǒng)、數(shù)據(jù)庫都暴露了。甚至目標(biāo)代碼會修改eval函數(shù)原型,埋入陷阱等等。

function也有一個安全問題就是可以修改全局變量,所有這種new Function的代碼執(zhí)行時的作用域為全局作用域,不論它的在哪個地方調(diào)用的,它訪問的都是全局變量。

所以也有一定的安全隱患,接下來我們的主角vm模塊登場。

安全性

使用vm的模塊會比eval更為安全,因為vm模塊運行的腳本完全無權(quán)訪問外部作用域(或自行設(shè)置一個有限的作用域)。 腳本仍在同一進(jìn)程中運行,因此為了獲得最佳安全性。當(dāng)然你可以給上下文傳入一些通用的API方便開發(fā):

vm.runInNewContext(`
  const util = require(‘util’);
  console.log(util);
`, {
  require: require,
  console: console
});

此外,另一個開源庫vm2針對vm的安全性等方面做了更多的提升,vm2。避免了一些運行腳本有可能“逃出”沙盒運行的邊緣情況,語法也跟易于上手,很推薦使用。

包管理

npm的包管理機(jī)制你一定要了解,不僅僅是node需要,我們前端瀏覽器項目本身也會引用很多第三方模塊。面試必備知識點。

下圖摘自抖音前端團(tuán)隊的npm包管理機(jī)制node模塊相關(guān)的面試題及答案有哪些

本圖如果你理解的話,后面的內(nèi)容就不用看了。

講npm install 要從嵌套結(jié)構(gòu)講起

嵌套結(jié)構(gòu)

在 npm 的早期版本中,npm 處理依賴的方式簡單粗暴,以遞歸的方式,嚴(yán)格按照 package.json 結(jié)構(gòu)以及子依賴包的 package.json 結(jié)構(gòu)將依賴安裝到他們各自的 node_modules 中。

如下圖:

node模塊相關(guān)的面試題及答案有哪些

這樣的方式優(yōu)點很明顯, node_modules 的結(jié)構(gòu)和 package.json 結(jié)構(gòu)一一對應(yīng),層級結(jié)構(gòu)明顯,并且保證了每次安裝目錄結(jié)構(gòu)都是相同的。

從上圖這種情況,我們不難得出嵌套結(jié)構(gòu)擁有以下缺點:

  • 在不同層級的依賴中,可能引用了同一個模塊,導(dǎo)致大量冗余

  • 在 Windows 系統(tǒng)中,文件路徑最大長度為260個字符,嵌套層級過深可能導(dǎo)致不可預(yù)知的問題

扁平結(jié)構(gòu)

2016 年,yarn 誕生了。yarn 解決了 npm 幾個最為迫在眉睫的問題:

  • 安裝太慢(加緩存、多線程)

  • 嵌套結(jié)構(gòu)(扁平化)

  • 無依賴鎖(yarn.lock)

  • yarn 帶來對的扁平化結(jié)構(gòu):

如下圖,我們簡單看下什么是扁平化的結(jié)構(gòu):

沒錯,這就是扁平化依賴管理的結(jié)果。相比之前的嵌套結(jié)構(gòu),現(xiàn)在的目錄結(jié)構(gòu)類似下面這樣: 假如之前嵌套的結(jié)構(gòu)如下:

node_modules
├─ a
|  ├─ index.js
|  |- node_modules -└─ b
|  |                ├─ index.js
|  |                └─ package.json
|  └─ package.json

那么扁平化處理以后,就編程下面這樣,被拍平了

node_modules
├─ a
|  ├─ index.js
|  └─ package.json
└─ b
   ├─ index.js
   └─ package.json

但是扁平化的結(jié)構(gòu)又會引出新的問題:

最主要的就是依賴結(jié)構(gòu)的不確定性!

啥意思,我就懶得畫圖了,拿網(wǎng)上的一個例子來說:

想象一下有一個 library-a,它同時依賴了 library-b、c、d、e:

node模塊相關(guān)的面試題及答案有哪些

而 b 和 c 依賴了 f@1.0.0,d 和 e 依賴了 f@2.0.0:

node模塊相關(guān)的面試題及答案有哪些

這時候,node_modules 樹需要做出選擇了,到底是將 f@1.0.0 還是 f@2.0.0 扁平化,然后將另一個放到嵌套的 node_modules 中?

答案是:具體做那種選擇將是不確定的,取決于哪一個 f 出現(xiàn)得更靠前,靠前的那個將被扁平化。

還有一個問題就是幽靈依賴,明明只安裝a包,你卻可以引用b包,因為a引用了b,并且扁平化處理了。

lock文件

這就是為啥要有l(wèi)ock文件的原因,lock文件可以保證安裝包的扁平化結(jié)構(gòu)的穩(wěn)定。

使用新的npm包管理工具?

pnpm? 可以簡單介紹一下為啥它能解決上面扁平化結(jié)構(gòu)和幽靈依賴的問題。

補(bǔ)充問題

  • a.js 和 b.js 兩個文件互相 require 是否會死循環(huán)? 雙方是否能導(dǎo)出變量? 如何從設(shè)計上避免這種問題?

答:不會, 先執(zhí)行的導(dǎo)出其 未完成的副本, 通過導(dǎo)出工廠函數(shù)讓對方從函數(shù)去拿比較好避免. 模塊在導(dǎo)出的只是 var module = { exports: {...} }; 中的 exports。以下摘自阮一峰老師的博客:

CommonJS模塊的重要特性是加載時執(zhí)行,即腳本代碼在require的時候,就會全部執(zhí)行。CommonJS的做法是,一旦出現(xiàn)某個模塊被"循環(huán)加載",就只輸出已經(jīng)執(zhí)行的部分,還未執(zhí)行的部分不會輸出。

讓我們來看,官方文檔里面的例子。腳本文件a.js代碼如下。

exports.done = false;
var b = require('./b.js');
console.log('在 a.js 之中,b.done = %j', b.done);
exports.done = true;
console.log('a.js 執(zhí)行完畢');

上面代碼之中,a.js腳本先輸出一個done變量,然后加載另一個腳本文件b.js。注意,此時a.js代碼就停在這里,等待b.js執(zhí)行完畢,再往下執(zhí)行。

再看b.js的代碼。

exports.done = false;
var a = require('./a.js');
console.log('在 b.js 之中,a.done = %j', a.done);
exports.done = true;
console.log('b.js 執(zhí)行完畢');

上面代碼之中,b.js執(zhí)行到第二行,就會去加載a.js,這時,就發(fā)生了"循環(huán)加載"。系統(tǒng)會去a.js模塊對應(yīng)對象的exports屬性取值,可是因為a.js還沒有執(zhí)行完,從exports屬性只能取回已經(jīng)執(zhí)行的部分,而不是最后的值。

a.js已經(jīng)執(zhí)行的部分,只有一行。

exports.done = false; 因此,對于b.js來說,它從a.js只輸入一個變量done,值為false。

然后,b.js接著往下執(zhí)行,等到全部執(zhí)行完畢,再把執(zhí)行權(quán)交還給a.js。于是,a.js接著往下執(zhí)行,直到執(zhí)行完畢。我們寫一個腳本main.js,驗證這個過程。

var a = require('./a.js');
var b = require('./b.js');
console.log('在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done);

執(zhí)行main.js,運行結(jié)果如下。

$ node main.js

在 b.js 之中,a.done = false
b.js 執(zhí)行完畢
在 a.js 之中,b.done = true
a.js 執(zhí)行完畢
在 main.js 之中, a.done=true, b.done=true

上面的代碼證明了兩件事。一是,在b.js之中,a.js沒有執(zhí)行完畢,只執(zhí)行了第一行。二是,main.js執(zhí)行到第二行時,不會再次執(zhí)行b.js,而是輸出緩存的b.js的執(zhí)行結(jié)果,即它的第四行。

exports.done = true;

ES6模塊的循環(huán)加載

ES6模塊的運行機(jī)制與CommonJS不一樣,它遇到模塊加載命令import時,不會去執(zhí)行模塊,而是只生成一個引用。等到真的需要用到時,再到模塊里面去取值。

因此,ES6模塊是動態(tài)引用,不存在緩存值的問題,而且模塊里面的變量,綁定其所在的模塊。請看下面的例子。

// m1.js
export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);
// m2.js
import {foo} from './m1.js';
console.log(foo);
setTimeout(() => console.log(foo), 500);

上面代碼中,m1.js的變量foo,在剛加載時等于bar,過了500毫秒,又變?yōu)榈扔赽az。

讓我們看看,m2.js能否正確讀取這個變化。

$ babel-node m2.js

bar
baz

上面代碼表明,ES6模塊不會緩存運行結(jié)果,而是動態(tài)地去被加載的模塊取值,以及變量總是綁定其所在的模塊。

這導(dǎo)致ES6處理"循環(huán)加載"與CommonJS有本質(zhì)的不同。ES6根本不會關(guān)心是否發(fā)生了"循環(huán)加載",只是生成一個指向被加載模塊的引用,需要開發(fā)者自己保證,真正取值的時候能夠取到值。

請看下面的例子(摘自 Dr. Axel Rauschmayer 的《Exploring ES6》)。

// a.js
import {bar} from './b.js';
export function foo() {
  bar();  
  console.log('執(zhí)行完畢');
}
foo();
// b.js
import {foo} from './a.js';
export function bar() {  
  if (Math.random() > 0.5) {
    foo();
  }
}

按照CommonJS規(guī)范,上面的代碼是沒法執(zhí)行的。a先加載b,然后b又加載a,這時a還沒有任何執(zhí)行結(jié)果,所以輸出結(jié)果為null,即對于b.js來說,變量foo的值等于null,后面的foo()就會報錯。

但是,ES6可以執(zhí)行上面的代碼。

$ babel-node a.js

執(zhí)行完畢

a.js之所以能夠執(zhí)行,原因就在于ES6加載的變量,都是動態(tài)引用其所在的模塊。只要引用是存在的,代碼就能執(zhí)行。

  • 如果 a.js require 了 b.js, 那么在 b 中定義全局變量 t = 111 能否在 a 中直接打印出來?

會,作用域鏈的嘛。

讀到這里,這篇“node模塊相關(guān)的面試題及答案有哪些”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領(lǐng)會,如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。

分享標(biāo)題:node模塊相關(guān)的面試題及答案有哪些
轉(zhuǎn)載注明:http://chinadenli.net/article20/gigojo.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供虛擬主機(jī)、網(wǎng)站設(shè)計、軟件開發(fā)、域名注冊、電子商務(wù)、建站公司

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

手機(jī)網(wǎng)站建設(shè)