本作品采用知識共享署名 4.0 國際許可協議進行許可。轉載保留聲明頭部與原文鏈接https://luzeshu.com/blog/nodesource3
本博客同步在https://cnodejs.org/topic/56e3dfde545c5c736d12383f
本博客同步在http://www.cnblogs.com/papertree/p/5225994.html
我們用慣了process.nextTick()、require('xxx')、module.exports,但是它們哪里來呢?下文給出答案...
3.1 node main函數到執行js文件的位置
3.1.1 入口
上篇博客2.1中提到src目錄存放的是node的C++源碼部分,包括main函數。
src/node_main.cc 和 src/node.cc(注:.cc是linux下的C++文件,類似windows下的.cpp)這兩個文件就是node的入口文件。
其中node_main.cc里面僅僅作為一個入口,調用node.cc 文件中的node::Start()。

圖3-1-1
3.1.2 node::Start()到加載js文件
有興趣可以看一下node::Start()函數做些什么(代碼截圖放上來了),我們關注的只是里面的StartNodeInstance()這一行。

圖3-1-2
我們來看一下一連串的調用:
Start() -> StartNodeInstance() -> LoadEnviroment() -> ExecuteString()
這四個函數都在node.cc文件里面,看到LoadEnviroment() 幾行關鍵代碼:

圖3-1-3
最終在LoadEnvrioment()里面加載node.js文件,調用ExecuteString()。
並且在ExecuteString()調用V8的 Script::Compile() 和 Script::Run()兩個接口去解析執行js代碼。
3.1.3 node.js文件和用戶的app.js文件
通過命令行“node app.js”啟動,我們希望node執行的是app.js 文件,為什么LoadEnvironment()里面加載的是node.js文件呢?
上篇博客2.1 講到node的lib文件夾存放原生js模塊,而src文件夾里面全部是.cc文件(node C++源碼) ,但src下面有一個node.js文件。這個文件的作用是什么呢?
3.2 node.js文件,包裝app.js文件,require/module.exports
先來看node.js文件的結構:

圖 3-2-1
3.2.1 node.js 返回的是一個函數
看到3.1.2 中,在LoadEnvironment()里面調用ExecuteString() 解析執行node.js文件,返回值是一個f_value。
而這個f_value通過V8的接口 Local<Function>::Cast轉換成一個Local<Function>類型的變量f ,而Local<Function>類型是V8中表示一個js 函數的C++類型。
在LoadEnvironment()的最后一行通過 f->Call(),去執行node.js返回來的一個函數。
而從node.js文件中也可以看出確實返回的是一個匿名函數。
所以,我們的app.js並不在ExecuteString()里面執行,而是在f->Call()的時候被執行。
3.2.2 LoadEnvironment()之f->Call()與 node.js之startup()
上面說到f->Call()的時候,是在V8里面解析執行了node.js返回來的匿名函數,來看一下該函數(圖3-2-1)。
步驟1. 匿名函數的第一個步驟是給startup()函數掛載一堆初始化函數,比如processNext()。
步驟2. startup的相關初始化函數都掛載完之后,匿名函數執行最后一步,調用startup()。
步驟3. 看到startup()函數的定義,里面開始去執行這一堆掛載函數。processNext()里面所做的就是給process.nextTick賦值。就是我們平時用的process.nextTick了。
注:匿名函數的參數process是C++模塊傳進來的對象(也就是f->Call()傳進來的參數)。
步驟4. startup()函數在執行完匿名函數掛載的一堆初始化函數之后,繼而執行Module.runMain()。
3.2.3 Module.runMain()包裝app.js文件
看到startup()函數在執行runMain()之前,對process.argv[1]里做了一下處理,這個argv就是node命令行啟動的時候,main函數接收的參數。比如“node app.js”啟動,argv[1]就保存着我們的js文件名了。
那么runMain()通過process.argv[1]去讀取app.js文件,讀成字符串,調用NativeModule.wrap()函數(圖3-2-1),把我們的app.js代碼包在里面。
這里就可以說明app.js代碼里面為何生來就有require、exports、module這幾個變量可以用了。其實是被放到一個 js函數里面了。
最后runMain()里面也是調用V8的Compile()、Run()等接口,去執行app.js代碼。

