1. 程式人生 > >【深入淺出Node.js系列三】深入Node.js的模組機制

【深入淺出Node.js系列三】深入Node.js的模組機制

1 Node.js模組的實現

之前在網上查閱了許多介紹Node.js的文章,可惜對於Node.js的模組機制大都著墨不多。在後續介紹模組的使用之前,我認為有必要深入一下Node.js的模組機制

1.1 CommonJS規範

早在Netscape誕生不久後,JavaScript就一直在探索本地程式設計的路,Rhino是其代表產物。無奈那時服務端JavaScript走的路均是參考眾多伺服器端語言來實現的,在這樣的背景之下,一沒有特色,二沒有實用價值。但是隨著JavaScript在前端的應用越來越廣泛,以及服務端JavaScript的推動,JavaScript現有的規範十分薄弱,不利於JavaScript大規模的應用。那些以JavaScript為宿主語言的環境中,只有本身的基礎原生物件和型別,更多的物件和API都取決於宿主的提供,所以,我們可以看到JavaScript缺少這些功能:

  • JavaScript沒有模組系統。沒有原生的支援密閉作用域或依賴管理。
  • JavaScript沒有標準庫。除了一些核心庫外,沒有檔案系統的API,沒有IO流API等。
  • JavaScript沒有標準介面。沒有如Web Server或者資料庫的統一介面。
  • JavaScript沒有包管理系統。不能自動載入和安裝依賴。

於是便有了CommonJS(http://www.commonjs.org)規範的出現,其目標是為了構建JavaScript在包括Web伺服器,桌面,命令列工具,及瀏覽器方面的生態系統。CommonJS其實不是一門新的語言,甚至都不能說它是一個新的直譯器——實際上它只是一個概念或者是一個規範

在這個規範中,它定義了很多 API ,講通俗點或者直截了當點就是函式啊類啊什麼的,而這些 API 是為那些普通應用程式(Native App)而非瀏覽器應用使用

。它的終極目標就是提供一個類似於 Python、Ruby 之類的指令碼一樣的標準庫,開發者可以用這樣的東西一樣來做到 Python、Ruby 能做到的事,而非僅僅侷限於網頁中的效果或者功能實現,它也可以跑在本地。所以說下面的事情對於 JavaScript 來說不再是夢:

服務端JavaScript應用

命令列工具

圖形介面應用

混合應用(Titanium、Adobe AIR等)

那麼,它具體彌補了 前端JavaScript 的哪些空白呢?其實這也涉及了很多 前端JavaScript 所沒有涉及的東西,如二進位制、編碼、IO、檔案、系統、斷言測試、套接字、事件佇列、Worker、控制檯等等

CommonJS制定瞭解決這些問題的一些規範,而Node.js就是這些規範的一種實現Node.js自身實現了require方法作為其引入模組的方法,同時NPM也基於CommonJS定義的包規範,實現了依賴管理和模組自動安裝等功能。這裡我們將深入一下Node.js的require機制和NPM基於包規範的應用。

1.2 簡單模組定義和使用

在Node.js中,定義一個模組十分方便。我們以計算圓形的面積和周長兩個方法為例,來表現Node.js中模組的定義方式。

var PI = Math.PI; 
exports.area = function(r){
    return PI * r * r;     
};
exports.circumference = function(r){ 
    return 2 * PI * r;
};

將這個檔案存為circle.js,並新建一個app.js檔案,並寫入以下程式碼:

var circle = require('./circle.js');
console.log('The area of a circle of radius 4 is' + circle.area(4));

可以看到模組呼叫也十分方便,只需要require需要呼叫的檔案即可

在require了這個檔案之後,定義在exports物件上的方法便可以隨意呼叫。Node.js將模組的定義和呼叫都封裝得極其簡單方便,從API對使用者友好這一個角度來說,Node.js的模組機制是非常優秀的。

例如把我們的伺服器指令碼放到一個叫做 start 的函式裡,然後我們會匯出這個函式。程式碼放在server.js檔案:

var http = require("http");

functionstart(){
    functiononRequest(request, response){
        console.log("Request received.");
        response.writeHead(200, {"Content-Type": "text/plain"});
        response.write("Hello World");
        response.end();
    }

    http.createServer(onRequest).listen(8888);
    console.log("Server has started.");
}

exports.start = start;

這樣,我們現在就可以建立我們的主檔案 index.js 並在其中啟動我們的HTTP了,雖然伺服器的程式碼還在 server.js 中。建立 index.js 檔案並寫入以下內容:

var server = require("./server");

server.start();

1.3 exports與module.exports的區別

上一節已經使用了用來建立函式的exports物件,來匯出一個模組(假設一個名為rocker.js的檔案):

exports.name = function() {
    console.log('My name is Lemmy Kilmister');
};

然後你在另一個檔案中呼叫:

var rocker = require('./rocker.js');
rocker.name(); // 'My name is Lemmy Kilmister'

但是module.exports到底是個什麼玩意兒? 它合法嗎?

令人吃驚的是-module.exports是真實存在的東西。exports只是module.exports的輔助方法。你的模組最終返回module.exports給呼叫者,而不是exports。exports所做的事情是收集屬性,如果module.exports當前沒有任何屬性的話,exports會把這些屬性賦予module.exports。如果module.exports已經存在一些屬性的話,那麼exports中所用的東西都會被忽略

把下面的內容放到rocker.js:

module.exports = 'ROCK IT!';
exports.name = function(){
    console.log('My name is Lemmy Kilmister');
};

然後把下面的內容放到另一個檔案中,執行它:

var rocker = require('./rocker.js');
rocker.name(); // TypeError: Object ROCK IT! has no method 'name'

rocker模組完全忽略了exports.name,然後返回了一個字串'ROCK IT!'。通過上面的例子,你可能認識到你的模組不一定非得是模組例項(module instances)。你的模組可以是任何合法的JavaScript物件 - boolean,number,date,JSON, string,function,array和其他。你的模組可以是任何你賦予module.exports的值。如果你沒有明確的給module.exports設定任何值,那麼exports中的屬性會被賦給module.exports中,然後並返回它

在下面的情況下,你的模組是一個類:

module.exports = function(name, age){
    this.name = name;
    this.age = age;
    this.about = function(){
        console.log(this.name +' is '+ this.age +' years old');
    };
};

然後你應該這樣使用它:

var Rocker = require('./rocker.js');
var r = new Rocker('Ozzy', 62);
r.about(); // Ozzy is 62 years old

在下面的情況下,你的模組是一個數組:

module.exports = ['Lemmy Kilmister', 'Ozzy Osbourne', 'Ronnie James Dio', 'Steven Tyler', 'Mick Jagger'];

然後你應該這樣使用它:

var rocker = require('./rocker.js');
console.log('Rockin in heaven: ' + rocker[2]); //Rockin in heaven: Ronnie James Dio

現在你應該找到要點了 - 如果你想要你的模組成為一個特別的物件型別,那麼使用module.exports;如果你希望你的模組成為一個傳統的模組例項(module instance),使用exports

把屬性賦予module.exports的結果與把屬性賦予給exports是一樣的。看下面這個例子:

module.exports.name = function() {
    console.log('My name is Lemmy Kilmister');
};

下面這個做的是一樣的事情:

exports.name = function() {
    console.log('My name is Lemmy Kilmister');
};

但是請注意,它們並不是一樣的東西。就像我之前說的module.exports是真實存在的東西,exports只是它的輔助方法。話雖如此,exports還是推薦的物件,除非你想把你模組的物件型別從傳統的模組例項(module instance)修改為其他的

1.4 模組載入策略

Node.js的模組分為兩類,一類為原生(核心)模組,一類為檔案模組原生模組在Node.js原始碼編譯的時候編譯進了二進位制執行檔案,載入的速度最快。另一類檔案模組是動態載入的,載入速度比原生模組慢。但是Node.js對原生模組和檔案模組都進行了快取,於是在第二次require時,是不會有重複開銷的。其中原生模組都被定義在lib這個目錄下面,檔案模組則不定性

node app.js

由於通過命令列載入啟動的檔案幾乎都為檔案模組。我們從Node.js如何載入檔案模組開始談起。載入檔案模組的工作,主要由原生模組module來實現和完成,該原生模組在啟動時已經被載入,程序直接呼叫到runMain靜態方法

// bootstrap main module.
Module.runMain = function(){
    // Load the main module--the command line arg
    Module._load(process.argv[1], null, true); 
};

_load靜態方法在分析檔名之後執行:

var module = new Module(id, parent);

並根據檔案路徑快取當前模組物件,該模組例項物件則根據檔名載入。

module.load(filename);

實際上在檔案模組中,又分為3類模組。這三類檔案模組以後綴來區分,Node.js會根據字尾名來決定載入方法:

.js。通過fs模組同步讀取js檔案並編譯執行。

.node。通過C/C++進行編寫的Addon。通過dlopen方法進行載入。

.json。讀取檔案,呼叫JSON.parse解析載入。

這裡我們將詳細描述js字尾的編譯過程。Node.js在編譯js檔案的過程中實際完成的步驟有對js檔案內容進行頭尾包裝。以app.js為例,包裝之後的app.js將會變成以下形式:

(function (exports, require, module, __filename, __dirname) {
    var circle = require('./circle.js');
    console.log('The area of a circle of radius 4 is ' + circle.area(4));
});

這段程式碼會通過vm原生模組的runInThisContext方法執行(類似eval,只是具有明確上下文,不汙染全域性),返回為一個具體的function物件最後傳入module物件的exports,require方法,module,檔名,目錄名作為實參並執行

這就是為什麼require並沒有定義在app.js檔案中,但是這個方法卻存在的原因。從Node.js的API文件中可以看到還有__filename、__dirname、module、 exports幾個沒有定義但是卻存在的變數。其中filename和dirname在查詢檔案路徑的過程中分析得到後傳入的。module變數是這個模組物件自身,exports是在module的建構函式中初始化的一個空物件({},而不是 null)。

在這個主檔案中,可以通過require方法去引入其餘的模組。而其實這個require方法實際呼叫的就是load方法

load方法在載入、編譯、快取了module後,返回module的exports物件。這就是circle.js檔案中只有定義在exports物件上的方法才能被外部呼叫的原因

以上所描述的模組載入機制均定義在lib/module.js中。

1.5 require方法中的檔案查詢策略

由於Node.js中存在4類模組(原生模組和3種檔案模組),儘管require方法極其簡單,但是內部的載入卻是十分複雜的,其載入優先順序也各自不同。

輸入圖片說明

  1. 從檔案模組快取中載入

    儘管原生模組與檔案模組的優先順序不同,但是都不會優先於從檔案模組的快取 中載入已經存在的模組。

  2. 從原生模組載入

    原生模組的優先順序僅次於檔案模組快取的優先順序。require方法在解析檔名之後,優先檢查模組是否在原生模組列表中。以http模組為例,儘管在目錄下存在一個http/http.js/http.node/http.json檔案,require(“http”)都不會從這些檔案中載入,而是從原生模組中載入。

    原生模組也有一個快取區,同樣也是優先從快取區載入。如果快取區沒有被載入過,則呼叫原生模組的載入方式進行載入和執行。

  3. 從檔案載入

    當檔案模組快取中不存在,而且不是原生模組的時候,Node.js會解析require方法傳入的引數,並從檔案系統中載入實際的檔案,載入過程中的包裝和編譯細節在前一節中已經介紹過,這裡我們將詳細描述查詢檔案模組的過程,其中, 也有一些細節值得知曉。

require方法接受以下幾種引數的傳遞:

http、fs、path等,原生模組。

/mod或../mod,相對路徑的檔案模組。

/pathtomodule/mod,絕對路徑的檔案模組。

mod,非原生模組的檔案模組。

在進入路徑查詢之前有必要描述一下modulepath這個Node.js中的概念對於每一個被載入的檔案模組,建立這個模組物件的時候,這個模組便會有一個paths屬性,其值根據當前檔案的路徑計算得到。我們建立modulepath.js這樣一個檔案,其內容為:

console.log(module.paths);

我們將其放到任意一個目錄中執行node modulepath.js命令,將得到以下的輸出結果:

[
    '/home/jackson/research/node_modules',
    '/home/jackson/node_modules',
    '/home/node_modules',
    '/node_modules'
]

可以看出module path的生成規則為:從當前檔案目錄開始查詢node_modules目錄;然後依次進入父目錄,查詢父目錄下的node_modules目錄;依次迭代, 直到根目錄下的node_modules目錄。

除此之外還有一個全域性module path,是當前node執行檔案的相對目錄 (../../lib/node)如果在環境變數中設定了HOME目錄和NODE_PATH目錄的話,整個路徑還包含NODE_PATH和HOME目錄下的.node_libraries 與.node_modules。其最終值大致如下:

[
    NODE_PATH,
    HOME/.node_modules,
    HOME/.node_libraries,
    execPath/../../lib/node
]

下圖是筆者從原始碼中整理出來的整個檔案查詢流程:

輸入圖片說明

簡而言之,如果require絕對路徑的檔案,查詢時不會去遍歷每一個node_modules目錄,其速度最快。其餘流程如下:

  1. 從modulepath陣列中取出第一個目錄作為查詢基準。

  2. 直接從目錄中查詢該檔案,如果存在,則結束查詢。如果不存在,則進行下一條查詢。

  3. 嘗試新增.js、.json、.node字尾後查詢,如果存在檔案,則結束查詢。如果不存在,則進行下一條。

  4. 嘗試將require的引數作為一個包來進行查詢,讀取目錄下的package.json檔案,取得main引數指定的檔案。

  5. 嘗試查詢該檔案,如果存在,則結束查詢。如果不存在,則進行第3條查詢。

  6. 如果繼續失敗,則取出modulepath陣列中的下一個目錄作為基準查詢,迴圈第1至5個步驟。

  7. 如果繼續失敗,迴圈第1至6個步驟,直到modulepath中的最後一個值。

  8. 如果仍然失敗,則丟擲異常。

整個查詢過程十分類似原型鏈的查詢和作用域的查詢。所幸Node.js對路徑查詢實現了快取機制,否則由於每次判斷路徑都是同步阻塞式進行,會導致嚴重的效能消耗。

1.6 包結構

前面提到,JavaScript缺少包結構。CommonJS致力於改變這種現狀,於是定義了包的結構規範(http://wiki.commonjs.org/wiki/Packages/1.0)。而NPM的出現則是為了在CommonJS規範的基礎上,實現解決包的安裝解除安裝,依賴管理,版本管理等問題。require的查詢機制明瞭之後,我們來看一下包的細節。

一個符合CommonJS規範的包應該是如下這種結構:

一個package.json檔案應該存在於包頂級目錄下。

二進位制檔案應該包含在bin目錄下。

JavaScript程式碼應該包含在lib目錄下。

文件應該在doc目錄下。

單元測試應該在test目錄下。

由上文的require的查詢過程可以知道,Node.js在沒有找到目標檔案時,會將當前目錄當作一個包來嘗試載入,所以在package.json檔案中最重要的一個欄位就是main。而實際上,這一處是Node.js的擴充套件,標準定義中並不包含此欄位, 對於require,只需要main屬性即可。但是在除此之外包需要接受安裝、解除安裝、依賴管理,版本管理等流程,所以CommonJS為package.json檔案定義瞭如下一些必須的欄位:

name : 包名,需要在NPM上是唯一的。不能帶有空格。

description : 包簡介。通常會顯示在一些列表中。

version : 版本號。一個語義化的版本號(http://semver.org/),通常為x.y.z。該版本號十分重要,常常用於一些版本控制的場合。

keywords : 關鍵字陣列。用於NPM中的分類搜尋。

maintainers : 包維護者的陣列。陣列元素是一個包含name、email、web三個屬性的JSON物件。

contributors : 包貢獻者的陣列。第一個就是包的作者本人。在開源社群,如果提交的patch被merge進master分支的話,就應當加上這個貢獻patch的人。格式包含name和email。

"contributors": [{
         "name": "Jackson Tian", "email": "mail @gmail.com"
     }, {
         "name": "fengmk2", "email": "mail2@gmail.com"
 }],

bugs : 一個可以提交bug的URL地址。可以是郵件地址 (mailto:[email protected]),也可以是網頁地址(http://url)。

licenses : 包所使用的許可證。例如:

"licenses": [{
     "type": "GPLv2",
     "url": "http://www.example.com/licenses/gpl.html",
 }]

repositories : 託管原始碼的地址陣列。

dependencies : 當前包需要的依賴。這個屬性十分重要,NPM會通過這個屬性,幫你自動載入依賴的包。

以下是Express框架的package.json檔案,值得參考:

{
  "_args": [
    [
      "[email protected]~4.13.1",
      "/Users/TaoBangren/[email protected]/king-node"
    ]
  ],
  "_from": "[email protected]>=4.13.1 <4.14.0",
  "_id": "[email protected]",
  "_inCache": true,
  "_installable": true,
  "_location": "/express",
  "_npmUser": {
    "email": "[email protected]",
    "name": "dougwilson"},
  "_npmVersion": "1.4.28",
  "_phantomChildren": {},
  "_requested": {
    "name": "express",
    "raw": "[email protected]~4.13.1",
    "rawSpec": "~4.13.1",
    "scope": null,
    "spec": ">=4.13.1 <4.14.0",
    "type": "range"},
  "_requiredBy": [
    "/"
  ],
  "_resolved": "https://registry.npmjs.org/express/-/express-4.13.3.tgz",
  "_shasum": "ddb2f1fb4502bf33598d2b032b037960ca6c80a3",
  "_shrinkwrap": null,
  "_spec": "[email protected]~4.13.1",
  "_where": "/Users/TaoBangren/[email protected]/king-node",
  "author": {
    "email": "[email protected]",
    "name": "TJ Holowaychuk"},
  "bugs": {
    "url": "https://github.com/strongloop/express/issues"},
  "contributors": [
    {
      "name": "Aaron Heckmann",
      "email": "[email protected]"},
    {
      "name": "Ciaran Jessup",
      "email": "[email protected]"},
    {
      "name": "Douglas Christopher Wilson",
      "email": "[email protected]"},
    {
      "name": "Guillermo Rauch",
      "email": "[email protected]"},
    {
      "name": "Jonathan Ong",
      "email": "[email protected]"},
    {
      "name": "Roman Shtylman",
      "email": "[email protected]"},
    {
      "name": "Young Jae Sim",
      "email": "[email protected]"}
  ],
  "dependencies": {
    "accepts": "~1.2.12",
    "array-flatten": "1.1.1",
    "content-disposition": "0.5.0",
    "content-type": "~1.0.1",
    "cookie": "0.1.3",
    "cookie-signature": "1.0.6",
    "debug": "~2.2.0",
    "depd": "~1.0.1",
    "escape-html": "1.0.2",
    "etag": "~1.7.0",
    "finalhandler": "0.4.0",
    "fresh": "0.3.0",
    "merge-descriptors": "1.0.0",
    "methods": "~1.1.1",
    "on-finished": "~2.3.0",
    "parseurl": "~1.3.0",
    "path-to-regexp": "0.1.7",
    "proxy-addr": "~1.0.8",
    "qs": "4.0.0",
    "range-parser": "~1.0.2",
    "send": "0.13.0",
    "serve-static": "~1.10.0",
    "type-is": "~1.6.6",
    "utils-merge": "1.0.0",
    "vary": "~1.0.1"},
  "description": "Fast, unopinionated, minimalist web framework",
  "devDependencies": {
    "after": "0.8.1",
    "body-parser": "~1.13.3",
    "connect-redis": "~2.4.1",
    "cookie-parser": "~1.3.5",
    "cookie-session": "~1.2.0",
    "ejs": "2.3.3",
    "express-session": "~1.11.3",
    "istanbul": "0.3.17",
    "jade": "~1.11.0",
    "marked": "0.3.5",
    "method-override": "~2.3.5",
    "mocha": "2.2.5",
    "morgan": "~1.6.1",
    "multiparty": "~4.1.2",
    "should": "7.0.2",
    "supertest": "1.0.1",
    "vhost": "~3.0.1"},
  "directories": {},
  "dist": {
    "shasum": "ddb2f1fb4502bf33598d2b032b037960ca6c80a3",
    "tarball": "http://registry.npmjs.org/express/-/express-4.13.3.tgz"},
  "engines": {
    "node": ">= 0.10.0"},
  "files": [
    "History.md"
            
           

相關推薦

深入淺出Node.js系列深入Node.js模組機制

1 Node.js模組的實現 之前在網上查閱了許多介紹Node.js的文章,可惜對於Node.js的模組機制大都著墨不多。在後續介紹模組的使用之前,我認為有必要深入一下Node.js的模組機制。 1.1 CommonJS規範 早在Netscape誕生不久後,JavaScr

數字影象處理系列影象增強:線性、 分段線性、 對數、 反對數、 冪律(伽馬)變換、直方圖均衡

本系列python版本:python3.5.4 本系列opencv-python版本:opencv-python3.4.2.17 本系列使用的開發環境是jupyter notebook,是一個python的互動式開發環境,測試十分方便,並集成了vim操作,安

知識深入理解js閉包

nts 存在 window 依次 ner hat 再看 tex 程序 本文轉載: 一、變量的作用域 要理解閉包,首先必須理解Javascript特殊的變量作用域。 變量的作用域無非就是兩種:全局變量和局部變量。 Javascript語言的特殊之處,就在於函數內部可以直接讀取

深度相機系列深度相機原理揭祕--雙目立體視覺

本文已經首發在個人微信公共號:計算機視覺life(微訊號CV_life),歡迎關注! 導讀 為什麼非得用雙目相機才能得到深度? 雙目立體視覺深度相機的工作流程 雙目立體視覺深度相機詳細工作原理   &

無人駕駛系列基於計算機視覺的無人駕駛感知系統

本文是無人駕駛技術系列的第三篇,著重介紹基於計算機視覺的無人駕駛感知系統。在現有的無人駕駛系統中,LiDAR是當仁不讓的感知主角。但是由於LiDAR的成本高等因素,業界有許多是否可以使用成本較低的攝像頭去承擔更多感知任務的討論。本文探索了基於計算機視覺的無人駕駛感知方案。首先,驗證一個方案是否可行需

學習筆記深入理解js原型和閉包(3)——prototype原型

既typeof之後的另一位老朋友! prototype也是我們的老朋友,即使不瞭解的人,也應該都聽過它的大名。如果它還是您的新朋友,我估計您也是javascript的新朋友。   在咱們的第一節(深入理解js原型和閉包(1)——一切皆是物件)中說道,函式也是一種物件。他也是屬性的集合,你也可以

學習筆記深入理解js原型和閉包(8)——簡述執行上下文

什麼是“執行上下文”(也叫做“執行上下文環境”)?暫且不下定義,先看一段程式碼: 第一句報錯,a未定義,很正常。第二句、第三句輸出都是undefined,說明瀏覽器在執行console.log(a)時,已經知道了a是undefined,但卻不知道a是10(第三句中)。 在一段js程式碼拿過來真正一句一

學習筆記深入理解js原型和閉包(9)—— 簡述執行上下文

繼續上一篇文章(https://www.cnblogs.com/lauzhishuai/p/10078231.html)的內容。 上一篇我們講到在全域性環境下的程式碼段中,執行上下文環境中有如何資料: 變數、函式表示式——變數宣告,預設賦值為undefined; this——賦值; 函式宣告

學習筆記深入理解js原型和閉包(11)——執行上下文棧

繼續上文的內容。 執行全域性程式碼時,會產生一個執行上下文環境,每次呼叫函式都又會產生執行上下文環境。當函式呼叫完成時,這個上下文環境以及其中的資料都會被消除,再重新回到全域性上下文環境。處於活動狀態的執行上下文環境只有一個。 其實這是一個壓棧出棧的過程——執行上下文棧。如下圖:   可

學習筆記深入理解js原型和閉包(12)——簡介作用域

提到作用域,有一句話大家(有js開發經驗者)可能比較熟悉:“javascript沒有塊級作用域”。所謂“塊”,就是大括號“{}”中間的語句。例如if語句: 再比如for語句: 所以,我們在編寫程式碼的時候,不要在“塊”裡面宣告變數,要在程式碼的一開始就宣告好了。以避免發生歧義。如: &nbs

學習筆記深入理解js原型和閉包(15)——閉包

前面提到的上下文環境和作用域的知識,除了瞭解這些知識之外,還是理解閉包的基礎。 至於“閉包”這個詞的概念的文字描述,確實不好解釋,我看過很多遍,但是現在還是記不住。 但是你只需要知道應用的兩種情況即可——函式作為返回值,函式作為引數傳遞。 第一,函式作為返回值 如上程式碼,bar函式作為返回值,賦

學習筆記深入理解js原型和閉包(17)——補this

本文對《深入理解js原型和閉包(10)——this》一篇進行補充,原文連結:https://www.cnblogs.com/lauzhishuai/p/10078307.html 原文中,講解了在javascript中this的各個情況,寫完之後發現還落下一種情況,就此補充。   原文中thi

學習筆記深入理解js原型和閉包(18)——補充:上下文環境和作用域的關係

本系列用了大量的篇幅講解了上下文環境和作用域,有些人反映這兩個是一回兒事。本文就用一個小例子來說明一下,作用域和上下文環境絕對不是一回事兒。   再說明之前,咱們先用簡單的語言來概括一下這兩個的區別。 00 上下文環境: 可以理解為一個看不見摸不著的物件(有若干個屬性),雖然看不見

重磅來襲:系列史上最全NB-IoT產業鏈方面的系列問題清單和聯盟答案

系列一中,我們分享了運營商部署NB-IoT的系列問題清單和聯盟答案,今天,小編不按常理出牌,即將分享物聯網各垂直應用領域裡,NB-IoT技術的部署,看看適合NB-IoT技術的垂直應用場景有哪些?垂直應用服務商又該如何部署?   3.1 NB-IoT適合的垂直應用場景有哪些?

cocos2d-js系列問題cocos2d-js建立幀動畫的兩種方法

不過在這之前,一定要先把圖片載入到記憶體當中去;其實你也可以不加,但是需要換另外一個函式就行; //將plist問價載入到記憶體當中 cc.spriteFrameCache.addSpriteFrames(res.play_plist); 第一種方法: 建立一個空的精靈

Node.js學習筆記-00 Node.js簡介

1.什麼是Node.js?Node.js不是JS應用,不是語言,也不是框架,只是JS的執行環境事件驅動,非阻塞I/O,簡單說就是每個函式都是非同步的,Node.js內部隱藏了非阻塞I/O的具體細節,使得我們可以輕鬆編寫高效能的WEB應用,所以它是輕量且高效的使用npm作為包管

Kafka踩坑系列之一消費者拉不出數據

dex -c 通知 還得 gin div 消費 發現 拉取 一、Bug背景 因業務需要,我們部署了兩個Kafka集群。Kafka集群A的版本號為:0.11.0.1,Kafka集群B的版本號為0.9.0.1。 因兩個Kafka集群的版本號不一致,嘗試了

Java TCP/IP Socket程式設計----深入剖析----TCP套接字生命週期

目錄   簡介 TCP連線 關閉TCP連線 解調多路複用 --------筆記來自於書籍《Java TCP/IP Socket程式設計》 簡介     新的Socket例項建立後(無論是通過公有的建構函式,或通過呼叫ServerSoc

Java TCP/IP Socket程式設計----深入剖析----TCP資料傳輸中的死鎖和效能

目錄   死鎖問題 資料傳輸效能 案例 --------筆記來自於書籍《Java TCP/IP Socket程式設計》 死鎖問題 在TCP資料傳輸底層實現中(詳細參見https://blog.csdn.net/lili13897741554/article/

Java TCP/IP Socket程式設計----深入剖析----TCP資料傳輸底層實現

目錄   套接字底層資料結構 TCP資料傳輸底層實現 案例 --------筆記來自於書籍《Java TCP/IP Socket程式設計》 套接字底層資料結構     要熟悉掌握網路程式設計,就需要理解套接字的具體實現所關聯的資料結構和底