Javascript里有个C:Part 2 – 对象
发布于 4年前 作者 fool 5140 次浏览 最后一次编辑是 2年前

Javascript里有个C系列文章:

  1. Javascript里有个C:Part 1 – 基础
  2. Javascript里有个C:Part 2 – 对象
  3. Javascript里有个C:Part 3 - 深入对象
  4. Javascript里有个C:Part 4 - 异步
  5. Javascript里有个C:Part 5 - node.js
  6. Javascript里有个C:Part 6 - 实战node.js Module

本文将详述Javascript的对象在C++中对应的实现,以及调用方法。文中叙述时,C++中的类型将加上命名空间v8::,以区分Javascript中的类型。

类型的表示

V8引擎实现中,Javascript中所有的对象、函数的继承关系都遵循ECMA-262。具体来讲, 所有变量的类型都是Value,继承v8::Value,对于Number、String、Boolean这样的Primitive类型,均继承v8::Primitive类。对象的类型为v8::Object,继承v8::Value,函数的类型为v8::Function,继承v8::Object。可见,几乎所有的Javascript类型在C++的表示中都具有相同的名称和继承关系。一张继承关系图可以大概描述:

不过有一点需要小小的注意,Javascript里对应同样的类型,可能既有Primitive类型,又有Object类型。比如下面的代码:

var a = "Hello World!";
var b = new String("Hello World!");

其中,a的类型是String,而b的类型却是String Object(参见ECMA-262, 4.3.18),在C++中它们对应的类型分别为v8::String和v8::StringObject。有同样性质的还有v8::NumberObject和v8::BooleanObject等。

Value

v8::Value是所有Javascript类型的基类,主要定义了大部分类型判断、类型转换的操作,还有将类型转换为字符串的操作。

另外v8::Value自身还继承v8::Data,主要作用是将构造函数私有化,以禁止直接在栈或者堆上构造对象。v8::Value及其派生类都由T::New ()来返回一个Handle<T>,以通过GC来管理对象的生命周期。

大致列举一下,主要有下面这些类型判断操作:

  • IsArray()
  • IsBoolean()
  • IsDate()
  • IsExternal()
  • IsFalse()
  • IsFunction()
  • IsInt32()
  • IsNull()
  • IsNumber()
  • IsObject()
  • IsString()
  • IsTrue()
  • IsUint32()
  • IsUndefined()

还有这些类型转换操作:

  • bool BooleanValue()
  • int32_t Int32Value()
  • int64_t IntegerValue()
  • double NumberValue()
  • Local<Boolean> ToBoolean()
  • Local<String> ToString()
  • Local<Int32> ToInt32()
  • Local<Integer> ToInteger()
  • Local<Number> ToNumber()
  • Local<Object> ToObject()
  • Local<Uint32> ToUint32()

此外不同的类型都拥有相当数量的特有操作,本文不再一一详述,具体可查手册或者直接看v8.h头文件

String

关于字符串可以捎带一提,通过使用Value::ToString()所有的类型几乎都是可以用字符串打印出来的(也就是浏览器和node.js里的console.log),虽然得到的可能只是类型的描述信息。而v8::String自身还包含两个子类,v8::String::Utf8Value和 v8::String::AsciiValue ,它们能够将 v8::String转换成const char *,使我们可以在C++中直接打印出一个Javascript变量的值。

Utf8Value和AsciiValue可以看成是对C字符串类型的简单封装,它接受一个Handle<Value>作为构造参数,通过对其解引用我们可以得到其C字符串表示,其代码很简单:

/**
* Converts an object to a utf8-encoded character array. Useful if
* you want to print the object. If conversion to a string fails
* (eg. due to an exception in the toString() method of the object)
* then the length() method returns 0 and the * operator returns
* NULL.
*/
class V8EXPORT Utf8Value {
public:
explicit Utf8Value(Handle obj);
~Utf8Value();
char* operator*() { return str_; }
const char* operator*() const { return str_; }
int length() const { return length_; }
private:
char* str_;
int length_;

// Disallow copying and assigning.
Utf8Value(const Utf8Value&);
void operator=(const Utf8Value&);
};

一个简单的例子如下:

  Handle<Array> a = Array::New(10);
  a->Set(0, String::New("I"));
  a->Set(1, String::New("Love"));
  a->Set(2, String::New("C++"));
  String::AsciiValue b (a);
  cout << *b;

首先,我们新建了一个数组,然后给数组赋值,接着通过String::AsciiValue将其转换成字符串,最后解引用将其转换成const char*,打印出来。

Object

在Javascript中,Object和Array本身就具有非常相似的特征,你甚至可以像下面这样混用操作(关于下面的代码提一句,里面的a和b是无法正确用JSON.stringify表示为JSON格式的,即便有的浏览器能,很多JSON库也是无法正常解析的):

var a = [];
a.prop = 1;
var b = {};
b[0] = 1;

实际在C++中,v8::Array直接继承v8::Object,所有Array的操作都在Object中进行了定义,二者几乎具有完全相同的接口。

Object里的属性,既可以通过下标来访问,又可以通过字符串索引来访问,上面的例子里使用的Array::Set便是一个下标访问的例子。Object里相关的接口定义如下:

  V8EXPORT bool Set(Handle<Value> key,
                    Handle<Value> value,
                    PropertyAttribute attribs = None);

  V8EXPORT bool Set(uint32_t index,
                    Handle<Value> value);

  V8EXPORT Local<Value> Get(Handle<Value> key);

  V8EXPORT Local<Value> Get(uint32_t index);

  V8EXPORT bool Has(Handle<Value> key);

  V8EXPORT bool Delete(Handle<Value> key);

  // Delete a property on this object bypassing interceptors and
  // ignoring dont-delete attributes.
  V8EXPORT bool ForceDelete(Handle<Value> key);

  V8EXPORT bool Has(uint32_t index);

  V8EXPORT bool Delete(uint32_t index);

  /**
   * Returns an array containing the names of the enumerable properties
   * of this object, including properties from prototype objects.  The
   * array returned by this method contains the same values as would
   * be enumerated by a for-in statement over this object.
   */
  V8EXPORT Local<Array> GetPropertyNames();

其中,Set和Get用来设置、获取property,Has和Delete用来删除property,GetPropertyNames可以获取对象的全部property。具体全部操作可以查看v8.h

举一个简单的例子,下面是一段Javascript代码:

var fool  = {};
fool.name = "I'm a fool";
fool.age  = 21;
fool.jobs = [];
fool.jobs.push ("SEU");
fool.jobs.push ("Taobao");

对应到C++的接口中,则是:

  Local<Object> fool = Object::New ();
  fool->Set (String::New ("name"), String::New ("I'm a fool"));
  fool->Set (String::New ("age"), Integer::New (21));
  fool->Set (String::New ("jobs"), Array::New ());
  Local<Object> jobs = fool->Get (String::New ("jobs"))->ToObject ();
  jobs->Set (0, String::New ("SEU"));
  jobs->Set (1, String::New ("Taobao"));
  // 打印结果
  cout << *String::AsciiValue (fool->Get (String::New ("name"))) << endl;
  cout << *String::AsciiValue (fool->Get (String::New ("age")))  << endl;
  cout << *String::AsciiValue (fool->Get (String::New ("jobs"))) << endl;

Accessors

上述对property的操作,和C++中直接操作数据成员的方法基本相同,自然,也有相同的限制。有时候一个属性代表的不完全是一个简简单单的变量,比如DOM里当你设置一个元素的id时,你并不单单是改变了某个字符串的值,整个DOM的渲染结果也会跟着改变。这种时候我们就需要Accessor来手动进行property的赋值和获取。

Object::SetAccessor的作用是为一个属性设置Getter和Setter函数,通过这两个函数来完成属性的获取、赋值操作。

下面是一个简单的例子,我们通过C++函数XGetter和XSetter来返回、赋值一个全局变量x:

int x = 10; 

Handle<Value> XGetter (Local<String> property, const AccessorInfo& info) {
    return Integer::New (x);
}

void XSetter (Local property<String>, Local<Value> value,
              const AccessorInfo& info) {
    x = value->Int32Value();
}

// 在main函数中

  Handle<Object> a = Object::New ();
  a->SetAccessor(String::New("x"), XGetter, XSetter);

  cout << x << endl;
  cout << *String::AsciiValue (a->Get (String::New ("x")));

最后运行代码后可以看到,最后两行的打印结果都是10。

5 回复

这是很有深度的系列文章,对理解V8的内部机制非常有帮助

[…] Javascript里有个C:Part 2 – 对象 […]

支持。只是参考文档有点老,如今V8 StringObject等都有相应的实现。http://jiangmiao.sinaapp.com/docs/v8/classv8_1_1Value.html,另外对于对象b={};b[0] = 1,一般来说都会把0转成字串处理,至少V8和SpiderMonkey都是,自然b也能通过JSON.stringify。

@jiangmiao 你的那份文档真是不错,之前苦觅良久,也只寻得一份上古材料,类图按新版更新上去了。b={};b[0] = 1现在确实都能正确处理,不过还是不鼓励这种方式吧。

[…] Javascript里有个C:Part 2 – 对象 […]

回到顶部