现在的位置: 首页 > 综合 > 正文

Google V8 里的InstanceTemplate和PrototypeTemplate

2012年08月18日 ⁄ 综合 ⁄ 共 1727字 ⁄ 字号 评论关闭

先看一段示例代码:

		static Handle<Value> Create(const Arguments& args){
			HandleScope store;
			Local<Object> self = args.Holder();
			return store.Close(self);
		}
		static void Load(Handle<Object>& glb,LPCWSTR name){
			HandleScope store;
			Handle<FunctionTemplate> ft = FunctionTemplate::New(&Create);
			Handle<ObjectTemplate> func = ft->PrototypeTemplate();
			Handle<ObjectTemplate> inst = ft->InstanceTemplate();
			glb->Set(String::New((uint16_t*)name),ft->GetFunction());
		}

这是一段典型的在C++下定义一个对象类型的代码,当我们在JavaScript下用new创建了一个这个类型的对象实例的时候,过程如下:

V8先创建一个Object对象实例,它就是将来要返回的对象,也就是Create函数里的self。但是在调用Create之前,先要对self做一些处理,这些处理依赖Load函数里的代码。凡是inst对象上的函数、属性或者访问器,都会复制给self。注意,这个过程是发生在Create函数调用之前的,如果在Create里对self做了一些特殊处理,而访问器被调用时受这些特殊处理影响,那么复制给self的过程中,就会调用访问器,而此时Create函数还没有调用,也就是说,不能假定Create是第一个被调用的函数。这是一种潜在的问题,因为Create函数总是在new被使用的时候被调用,会造成Create函数已经调用过,其它函数才会被调用的误解。

除了inst,func上的属性、函数或者访问器会被复制给原型对象。每一函数都有一个唯一的原型对象,所有使用new和这个函数创建的对象的__proto__也都指向这个唯一的原型对象。如果没有继承,这个原型对象就是一个Object,实际上所谓的继承也就是向这Object对象添加属性、函数和访问器的过程。基于这种原理,func上的属性、函数和访问器效率要高,因为原型只需要创建一次,后续的new都无需重新初始化。

原型是什么时候创建的呢?答案是:构造函数加载的时候,而不是第一个实例创建的时候。一个函数默认的prototype是一个Object对象,注意,每一个函数都有自己独立的原型对象。C++可以内部给这个Object对象添加属性,也就是PrototypeTemplate的属性。如果这个PrototypeTemplate有属性访问器,而且后续有访问器设置的同名属性被设置,此时会引发set访问器被回调。这一切都发生在函数(对象构造模板)Load的过程中,准确的说是这一句glb->Set(String::New((uint16_t*)name),ft->GetFunction());执行的时候。而inst上的属性,直到一个实例被创建,才会被回调。

这让我们看到,JavaScript语言异常简单,实际上所有的对象都是Object对象,不同的对象仅仅是new的时候自动添加了一些属性。而继承,就是一个原形链的访问规则——每一类对象都指向一个唯一的原型对象,这个原型对象又指向它的原型对象,这依赖于原型的类型。原型链的终点是一个全局Object对象,而这个Object对象的原型是null,我们通常创建的Object对象实例的原型也指向这个Object对象(而不是null),所以和这个全局Object是不同的。

附带说明一下args.Holder()和args.This()的区别。V8 函数的参数全部靠args传递,大多数情况下,会发现args.Holder()和args.This()是完全同一个对象,其实Holder是指呼叫的对象本身,也就是如果这个方法是原型上的,它表示这个原型对象,而不是呼叫的对象。而This返回的是呼叫的对象,而非方法所属的原型对象。

抱歉!评论已关闭.