不知不觉都快月底了,看了看上一篇仍是6号写的,惭愧惭愧,说好的坚持。为了证实没有偷懒(其实仍是沉迷了一会dota2),先上一个图自证清白。ajax
基本上从初始化引擎,到Isolate、handleScope、Context一直到编译其实都有记录,可是实在是无从下手。虽然说个人博客也没有什么教学意义,可是至少也须要有一个中心和结论。很遗憾,上述的每一步都并互有关联,也就是单独拿出来写毫无心义。而从总体架构来阐述,而后细化到这每一步,我又尚未到那个境界。所以,综合考虑下,决定先暂时放弃逐步解析,优先产出一些有意义的东西。数组
这一篇的内容属于V8中(或许是C++独有)使用比较广泛的一个技巧,不少模块都有使用。架构
当初在入门学JS的时候,到了ajax那里,跟着视频学封装。老师讲,若是参数过多,就包装成封装一个对象,这样只须要一个参数就能够了。当时我想着,一个对象也好麻烦啊,还不如封装的时候本身定义一下,若是传1,就表明是"GET"请求,传2,就表明"POST"等等。没想到,当初天真的想法,居然在C++里面实现了。app
下面开始正文,首先须要简单介绍一下枚举,话说各位用过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能够取到对应的值,而枚举偏偏相反,经过枚举值取到的是"下标"。大部分简单的配置状况下,是不用去关心枚举具体的值。这样,关于枚举就介绍完了,很简单。ui
接下来,来看看V8是如何利用这个数据类型来实现参数配置。在对JS源码字符串的编译过程当中,有一个类十分重要,负责记录String => AST的过程,名为ParseInfo,这里不去探究转换过程,单纯看一下这个类的标记配置相关,类定义以下。spa
namespace v8 { namespace interval { // A container for the inputs, configuration options, and outputs of parsing. /** * 有5个构造函数和大量私有属性 */ 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: // Various configuration flags for parsing. enum Flag { kToplevel = 1 << 0, kEager = 1 << 1, kEval = 1 << 2, kStrictMode = 1 << 3, kNative = 1 << 4, // ...more }; 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'等等。code
枚举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的类来管理。
由于实在太简单了,因此我也懒得画图,应该能理解吧。