不知不覺都快月底了,看了看上一篇仍是6號寫的,慚愧慚愧,說好的堅持。爲了證實沒有偷懶(其實仍是沉迷了一會dota2),先上一個圖自證清白。
基本上從初始化引擎,到Isolate、handleScope、Context一直到編譯其實都有記錄,可是實在是無從下手。雖然說個人博客也沒有什麼教學意義,可是至少也須要有一箇中心和結論。很遺憾,上述的每一步都並互有關聯,也就是單獨拿出來寫毫無心義。而從總體架構來闡述,而後細化到這每一步,我又尚未到那個境界。所以,綜合考慮下,決定先暫時放棄逐步解析,優先產出一些有意義的東西。
這一篇的內容屬於V8中(或許是C++獨有)使用比較廣泛的一個技巧,不少模塊都有使用。
當初在入門學JS的時候,到了ajax那裏,跟着視頻學封裝。老師講,若是參數過多,就包裝成封裝一個對象,這樣只須要一個參數就能夠了。當時我想着,一個對象也好麻煩啊,還不如封裝的時候本身定義一下,若是傳1,就表明是"GET"請求,傳2,就表明"POST"等等。沒想到,當初天真的想法,居然在C++裏面實現了。
下面開始正文,首先須要簡單介紹一下枚舉,話說各位用過TS的大佬應該都懂,或者接觸過protobuf這些數據格式化庫也有。枚舉在不少語言中都有,定義簡單說就是一系列的常量集合,一般用來作簡單配置。若是沒有指定值,那麼就是0、一、2...依次增長,舉例以下。
enum fruit {
apple,
banana,
pear,
orange = 5,
};
int main(int argc, const char * argv[]) {
cout << "enum apple is " << fruit::apple << endl;
cout << "enum banana is " << fruit::banana << endl;
cout << "enum pear is " << fruit::pear << endl;
cout << "enum orange is " << fruit::orange << endl;
return 0;
}複製代碼
這裏咱們定義了一個枚舉類型,依次打印每個值,會獲得0、一、2,而第四個因爲手動指定了值,因此會獲得5。若是不去手動指定值,從JS的角度來看枚舉有點相似於一個顛倒形式的數組,好比說定義['apple', 'banana', 'pear'],經過下標0、一、2能夠取到對應的值,而枚舉偏偏相反,經過枚舉值取到的是"下標"。大部分簡單的配置狀況下,是不用去關心枚舉具體的值。這樣,關於枚舉就介紹完了,很簡單。
接下來,來看看V8是如何利用這個數據類型來實現參數配置。在對JS源碼字符串的編譯過程當中,有一個類十分重要,負責記錄String => AST的過程,名爲ParseInfo,這裏不去探究轉換過程,單純看一下這個類的標記配置相關,類定義以下。
namespace v8 {
namespace interval {
class ParseInfo {
public:
explicit ParseInfo(AccountingAllocator* zone_allocator);
explicit ParseInfo(Isolate*);
ParseInfo(Isolate*, AccountingAllocator* zone_allocator);
ParseInfo(Isolate* isolate, Handle<Script> script);
ParseInfo(Isolate* isolate, Handle<SharedFunctionInfo> shared);
private:
enum Flag {
kToplevel = 1 << 0,
kEager = 1 << 1,
kEval = 1 << 2,
kStrictMode = 1 << 3,
kNative = 1 << 4,
};
unsigned flags_;
void SetFlag(Flag f) { flags_ |= f; }
void SetFlag(Flag f, bool v) { flags_ = v ? flags_ | f : flags_ & ~f; }
bool GetFlag(Flag f) const { return (flags_ & f) != 0; }
};複製代碼
省略了不少代碼,這個類真的超級大,特別是構造函數,雖然說內部走的Isolate那一個,可是變向的調用會走全套構造。目前只須要關心私有屬性枚舉Flag和其相關的三個方法,Flag負責標記編譯的代碼的一些特徵,好比說[native code]、module、IIFE、'strict mode'等等。
枚舉Flag的定義有點意思,除去了正常的語義化集合,每一項都給了具體的值,依次爲一、二、四、8...,後面會解釋緣由。flags_就表明了整個Flag的配置,類型比較狗,只註明了一個無符號類型,大部門狀況下編譯器會認爲是一個unsigned int。剩下的三個方法則是根據參數來調整flag_的值,具體實現很是簡單,可是理解起來有點噁心,全是位運算。
若是要理解這個操做的原理,須要從二進制的角度來理解,枚舉類型的每個值,其實表明的是二進制的一、十、100、1000等等,因此flags_其實也須要從二進制來理解,默認狀況是一個全0的數。這樣再來看SetFlag方法,假設解析中發現字符串"strict mode",此時須要調用SetFlag(Flag::kStrictMode)來設置參數,或運算表示只要有一個是1即置1,因此flags_的第4位會被置位1,值就變成了1000。
那麼GetFlag就很好理解了,傳入一個Flag枚舉值,因爲與運算須要兩個都是1纔會爲真,而傳入的總爲1,因此只要flag_對應的位爲1(即被設置過)就會返回真。
而SetFlag的重載方法則是一個擴展,當第二個參數爲true時,使用與單參數一致。當第二個參數爲false時,會將該位置0,也就是取消這個配置。
這樣,用一個數字就能夠表明很是多的編譯參數。在應用時,直接取出數字對應位數的值,若是爲1,說明該配置爲真,不然爲假,即簡單,又很高效。固然,這個方法的侷限性也很明顯,只能針對布爾值的配置,若是是複雜類型那仍是須要一個xxxoptions的類來管理。
由於實在太簡單了,因此我也懶得畫圖,應該能理解吧。