Javascript里有个C系列文章:
- Javascript里有个C:Part 1 – 基础
- Javascript里有个C:Part 2 – 对象
- Javascript里有个C:Part 3 - 深入对象
- Javascript里有个C:Part 4 - 异步
- Javascript里有个C:Part 5 - node.js
- 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。
这是很有深度的系列文章,对理解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 – 对象 […]
再挖个坟…
mark