原文地址:https://developer.mozilla.org/en-US/docs/SpiderMonkey/JSAPI_User_Guide
文章为郭胜龙所写,转载说明出处
本文是mozilla官网上关于spiderMonder的一篇用户指南,正好要用,顺带翻译了一下:
JSAPI 用户指南
全局对象:全局对象包括所有可被JS代码用的类,方法,变量。例如当js代码进行了类似window.open的操作,它会获取一个全局的属性,在这个例子中是window。JSAPI对可见的全局属性脚本有全权的控制。该应用程序通过创建一个对象,并用标准的JavaScript类类似Array和Object填充它来开始,然后它再把应用程序希望提供的自定义类、函数和变量(类似window);每次应用程序运行JS脚本(例如使用JS_EvaluateScript),它会提供一个全局的对象供这个脚本使用。当脚本运行时它会创建自己的全局的函数和变量。所有这些函数和变量以属性的形式储存在全局对象中。
一个简单的例子:
3个关键元素中每个都需要一些JSAPI调用:
runtime:使用JS_NewRuntime来创建并使用JS_DestroyRuntime来销毁。当你的应用不使用了,使用JS_ShutDown来释放所有缓存资源。(每次结束都调用是个好习惯!!!)
context:使用 JS_NewContext
and JS_DestroyContext。为了保证ECMAScript 标准的一致性,应用也需要使用JS_SetOptions来开启JSOPTION_VAROBJFIX。为了获得JS的新功能,应用可能会使用JS_SetVersion。错误报告也是每个context都需要并且通过启用JS_SetErrorReporter来使用。
golbal object:你需要一个设置了JSCLASS_GLOBAL_FLAGS选项的JSClass。下面的例子定义了一个非常简单的JSClass(叫global_class)没有自己的属性和 方法。使用JS_NewGlobalObject来创建一个全局对象。使用JS_InitStandardClasses来用标准的JS全局变量填充它。
JSAPI一般设计应用程序的规模会有多个线程,多个contexts和多个全局对象。它是细粒度的API,支持各种部分的组合,给应用对SpiderMonkey行为的绝对掌控。
下面是一个小例子,包含上面讨论的所有东西:
#include "jsapi.h"
/* The class of the global object. */
static JSClass global_class = { "global",
JSCLASS_NEW_RESOLVE | JSCLASS_GLOBAL_FLAGS,
JS_PropertyStub,
JS_DeletePropertyStub,
JS_PropertyStub,
JS_StrictPropertyStub,
JS_EnumerateStub,
JS_ResolveStub,
JS_ConvertStub,
NULL,
JSCLASS_NO_OPTIONAL_MEMBERS
};
/* The error reporter callback. */
void reportError(JSContext *cx, const char *message, JSErrorReport *report) {
fprintf(stderr, "%s:%u:%s\n",
report->filename ? report->filename : "[no filename]",
(unsigned int) report->lineno,
message);
}
int run(JSContext *cx) {
/* Enter a request before running anything in the context */
JSAutoRequest ar(cx);
/* Create the global object in a new compartment. */
JSObject *global = JS_NewGlobalObject(cx, &global_class, NULL);
if (global == NULL)
return 1;
/* Set the context's global */
JSAutoCompartment ac(cx, global);
JS_SetGlobalObject(cx, global);
/* Populate the global object with the standard globals, like Object and Array. */
if (!JS_InitStandardClasses(cx, global))
return 1;
/* Your application code here. This may include JSAPI calls to create your own custom JS objects and run scripts. */
return 0;
}
int main(int argc, const char *argv[]) {
/* Initialize the JS engine -- new/required as of SpiderMonkey 31. */
if (!JS_Init())
return 1;
/* Create a JS runtime. */
JSRuntime *rt = JS_NewRuntime(8L * 1024L * 1024L, JS_NO_HELPER_THREADS);
if (rt == NULL)
return 1;
/* Create a context. */
JSContext *cx = JS_NewContext(rt, 8192);
if (cx == NULL)
return 1;
JS_SetOptions(cx, JSOPTION_VAROBJFIX);
JS_SetErrorReporter(cx, reportError);
int status = run(cx);
JS_DestroyContext(cx);
JS_DestroyRuntime(rt);
/* Shut down the JS engine. */
JS_ShutDown();
return status;
}
每个JSNative有相同的签名,忽视期望js传回的参数。
传给函数的js参数在argc和vp.argc中给出,告诉它回调传了几个参数,JS_ARGV(cx, vp)返回这些参数的一个数组。参数并没有原生的C++类型类似int 和 float;他们是jsval类型。原生的函数使用JS_ConvertArguments来转换参数为C++类型并将他们储存在本地变量中。本地函数使用JS_SET_RVAL(cx, vp, val)来存储它的js返回值。
成功的前提是JSNative必须调用JS_SET_RVAL并且返回JS_TRUE。传给JS_SET_RVAL的值在js回调中返回。
失败的话JSNative调用一个错误报告函数,在这个例子中为JS_ReportError,并且返回JS_FALSE。这样会使异常抛出。调用可以被Try/catch捕捉到。
为了让原生的函数可以被js调用,声明一个JSFunctionSpec表来描述这个函数,然后调用JS_DefineFunctions
.
static JSFunctionSpec myjs_global_functions[] = { JS_FS("rand", myjs_rand, 0, 0), JS_FS("srand", myjs_srand, 0, 0), JS_FS("system", myjs_system, 1, 0), JS_FS_END }; ... if (!JS_DefineFunctions(cx, global, myjs_global_functions)) return JS_FALSE; ...一旦函数在golbal中定义了,任何使用golbal的脚本可以调用他们,就像任何的web网页可以调用alert一样。在我们创建的环境中,“hello world”应该这样写:
system("echo hello world");JSAPI概念
这个版块主要目的是提出JSAPI的重大缺陷,要用spiderMonkey开发必须要读完所有的额版块。
JavaScript values
主要的文章:JS::value
js是一个动态类型的语言:变量和属性没有在编译的时候修正的。那么类似c和C++这些所有变量都有类型的语言如何与js交互呢?JSAPI提供了一个数据类型,JS::Value(也有一个弃用的jsval类型),可以包含任何js值得类型。一个JS::value可以是一个数字、一个字符串和一个bool型的值,一个对象的引用(类似Object
, Array
, Date
, or Function
),或者一个特殊类型例如:null或者undefined。
对于整形和bool的值,jsval包含值,在其他情况下,jsval是一个object, string, or number指针。
jsval包含成员函数类检测js数据的类型。他们是是isObject()
,isNumber()
, isInt32()
, isDouble()
, isString()
, isBoolean()
, isNull()
, and isUndefined()
.
如果jsval包含一个JSObject,double,或者JSString,你可以把它通过成员函数toObject()
, toDouble()
, and toString()
转换成它的基本数据类型。你的应用或者JSAPI函数需要特殊数据类型的值和参数而不是jsval时会很有用。类似的,你可以创建JS::Value绑定一个JSObject、double,JSString指针到javal对象使用JS::ObjectValue(JSObject&)
,JS::DoubleValue(double)
或者JS::StringValue(JSString*).
垃圾回收
当运行时,js 代码隐式分配对象的内存,strings,变量等等。垃圾回收是当有片段的内存不可到达时通过js引擎检查的过程,就是说,他们不能再次使用,并且回收。
垃圾回收有两个重要的影响。第一,应用必须非常确定它需要的任何值都是GC可以到达的。垃圾回收机制对这些非常乐意处理。如果你们有告诉JSAPI你还会用它那么任何你留下的对象将被销毁。第二,应用应该减少垃圾收集的性能影响。
保持对象存在
如果你的JSAPI应用崩溃了,一般都是Gc相关的错误。应用必须确保垃圾回收器可以到达所有仍在使用的对象,数字,字符串。否则,GC将释放被这些值占用的内存,下次使用的时候导致程序崩溃。
下面有几种方法来保证值是GC-reachable。
1、如果你只是需要JSNative调用时限中保持可达,那么将其存储在*rval或者argv数组的一个元素中。储存在这些地方的值总是可达的。要获得更多额外的argx插槽,可以使用 JSFunctionSpec.extra
.
2、如果一个自定义的对象需要确定的值保持在内存中,只要将值储存在对象的属性中。对象和属性都将保持可达。如果这些值不需要用js调用,可以使用reserved slots来替代。或者将值储存在私有数据中并实现JSClass.mark。
3、如果一个函数创建了新的对象,string或者数字,它可以使用JS_EnterLocalRootScope和JS_LeaveLocalRootScope来保持这些值在函数的生命周期中存在。
4、要让值永久有效,将他储存在GC root中。
但不管如何,GC bug还是会发生。以下这两个函数(只在DEBUG环境下有效),对调试GC相关的崩溃特为的有用:
1、使用JS_SetGCZeal来开启额外垃圾回收。GC zeal通常会导致GC相关崩溃发生。只用于开发和调试,因为额外的垃圾回收是的js非常慢。
2、使用JS_DumpHeap来转存SpiderMonkey堆或者特殊的感兴趣部分。
GC的性能
过于频繁的垃圾回收会导致性能问题。一些应用可以通过增加JSRuntime的初始化大小来减少垃圾回收的次数。
但它影响到用户时大概最好的技术是在空闲的时间中进行垃圾回收。默认的,js引擎在没有别的选择只有发展过程。这个意味着垃圾回收一般在内存密集型代码运行时发生。应用可以随时通过调用JS_GC
or JS_MaybeGC
触发垃圾回收。
错误和异常
检查JSAPI函数返回值这一步骤的重要性是没的说的。差不多每个带有JSContext*参数的JSAPI函数都可以出错。系统可能会运行完内存。这里可能会有一个脚本的语法错误。或者脚本显式的抛出一个异常。
js语言和C++都有异常,但是他们不是相同的东西。SpiderMonkey不使用C++异常来处理东西。JSAPI函数从不抛出C++异常,当SpiderMonkey调用一个应用的回调时,回调必须不抛出C++异常。
抛出和捕捉异常
我们已经看了如何从一个JSNative函数中抛出异常。只需要简单调用JS_ReportError,加上
printf
的格式参数,并且返回JS_FALSE。
rc = system(cmd); if (rc != 0) { /* Throw a JavaScript exception. */ JS_ReportError(cx, "Command failed with exit code %d", rc); return JS_FALSE; }这很像JS语句 throw new Error("Command failed with exit code " + rc);。同样注意调用
JS_ReportError
不会导致C++异常抛出。它只会创建一个新的js错误对象并且在context中保存作为当前的挂起异常。应用同样返回JS_FALSE。
一旦C++函数返回JS_FALSE,js引擎开始展开js栈,寻找catch或者finally代码块来执行。
错误报告
自己要做的事自定义错误报告并且把我什么时候报告。
自动处理未捕获的异常
更多的例子
定义对象和属性
/* 静态初始化一个类来创建“一次性”对象. */ JSClass my_class = { "MyClass", /* 所有这些都可以用JS_*Stub函数指针来取代. */ my_addProperty, my_delProperty, my_getProperty, my_setProperty, my_enumerate, my_resolve, my_convert, my_finalize }; JSObject *obj; /* * 定义一个全局范围内的对象可以在for/in循环中枚举。 * 第二个参数为父对象,三、四参数和普通API调用一样是名字+类。 * 原型传递是空,所以将使用默认对象原型 */ obj = JS_DefineObject(cx, globalObj, "myObject", &my_class, NULL, JSPROP_ENUMERATE); /* * 用JSPropertySpec静态数组初始化定义了一堆属性,以{0}结束。除了名字,每个属性还有 * 一个“tiny”定义(例如MY_COLOR) 可以用在switch语句中(例如下文中的my_getProperty函数) */ enum my_tinyid { MY_COLOR, MY_HEIGHT, MY_WIDTH, MY_FUNNY, MY_ARRAY, MY_RDONLY }; static JSPropertySpec my_props[] = { {"color", MY_COLOR, JSPROP_ENUMERATE}, {"height", MY_HEIGHT, JSPROP_ENUMERATE}, {"width", MY_WIDTH, JSPROP_ENUMERATE}, {"funny", MY_FUNNY, JSPROP_ENUMERATE}, {"array", MY_ARRAY, JSPROP_ENUMERATE}, {"rdonly", MY_RDONLY, JSPROP_READONLY}, {0} }; JS_DefineProperties(cx, obj, my_props); /* * 鉴于上述定义和JS_DefineProperties的调用,obj将需要类似这种的“getter”方法在类中。 */ static JSBool my_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) { if (JSVAL_IS_INT(id)) { switch (JSVAL_TO_INT(id)) { case MY_COLOR: *vp = . . .; break; case MY_HEIGHT: *vp = . . .; break; case MY_WIDTH: *vp = . . .; break; case MY_FUNNY: *vp = . . .; break; case MY_ARRAY: *vp = . . .; break; case MY_RDONLY: *vp = . . .; break; } } return JS_TRUE; }定义类
通过定义一个构造函数可以将上述的API元素都集合起来:一个原型对象、原型和构造函数的属性都放到一个API调用中。
通过定义类的构造函数、原型、类属性初始化一个类,后者类比于java中的static。他们在构造函数对象的域中定义,所以只有new出来的额class才能使用MyClass.myStaticProp。
JS_InitClass有许多参数,但是你可以对最后四个参数传递NULL如果他们没有这些属性或者方法。
注意你不需要使用JS_InitClass来创建class的新实例,否则创建全局对象时会出现鸡与蛋的问题,但是当你需要一个构造函数通过new来调用时你应该调用JS_InitClass,以新的属性来延伸原型对象(MyClass.Prototype)。总的来说,如果你希望支持多个实例共享行为,使用JS_InitClass。
protoObj = JS_InitClass(cx, globalObj, NULL, &my_class, /* native constructor function and min arg count */ MyClass, 0, /* prototype object properties and methods -- these will be "inherited" by all instances through delegation up the instance's prototype link. */ my_props, my_methods, /* class constructor properties and methods */ my_static_props, my_static_methods);运行脚本
/* 这里指出源文件的地址. */ char *filename; uintN lineno; /* * The return value comes back here -- if it could be a GC thing, you must * add it to the GC's "root set" with JS_AddRoot(cx, &thing) where thing * is a JSString *, JSObject *, or jsdouble *, and remove the root before * rval goes out of scope, or when rval is no longer needed. */ jsval rval; JSBool ok; /* * Some example source in a C string. Larger, non-null-terminated buffers * can be used, if you pass the buffer length to JS_EvaluateScript. */ char *source = "x * f(y)";
/**
*编译并且执行
*/ ok = JS_EvaluateScript(cx, globalObj, source, strlen(source), filename, lineno, &rval); if (ok) { /* Should get a number back from the example source. */ jsdouble d; ok = JS_ValueToNumber(cx, rval, &d); . . . }
调用函数
/* Call a global function named "foo" that takes no arguments. */ ok = JS_CallFunctionName(cx, globalObj, "foo", 0, 0, &rval); jsval argv[2]; /* Call a function in obj's scope named "method", passing two arguments. */ argv[0] = . . .; argv[1] = . . .; ok = JS_CallFunctionName(cx, obj, "method", 2, argv, &rval);
1、同一时间只创建需要的context
2、一直保存他们比销毁重建要好得多
如果你的应用创建了多个runtimes,应用可能需要知道那个context对应那个runtime,在这种情况下,使用JS_GetRuntime。
使用JS_SetContextPrivate和JS_GetContextPrivate来和context关联应用特定的数据。
初始化内置对象和全局JS对象
对于SpiderMonkey提供的内置对象的完整的列表,参见S_InitStandardClasses
.
应用提供给脚本的全局对象决定了脚本能做什么。例如,火狐浏览器使用自己的全局对象:windows。调用JS_SetGlobalObject
.来改变全局对象。
创建并初始化自定义对象
除了使用引擎的内置对象,你还可以创建,初始化和使用自己的JS对象。你可以使用JS引擎自动化执行你的应用。自定义JS对象可以提供直接的程序服务,或者他们可以作为你的程序的服务接口。例如,一个自定义的JS对象,他提供的直接服务可能是处理一个应用程序的所有网络访问,或者作为数据库服务的中间件。或者一个数据镜像和函数都已经存在于应用中为C代码提供面向对象的接口或者严格来讲,面向对象本身。这些自定义对象作为应用程序本身的接口,将值从应用程序传递给用户,接受并且在返回给应用之前处理用户输入。这样的一个对象可能被用来提供一个访问基本功能的接口。
有两种方法来创建JS引擎可以使用的自定义对象:
1、编写一个JS脚本创建一个对象,脚本中有他的属性、方法和构造函数,然后再运行时把脚本传递给JS引擎。
2、在你的应用程序中嵌入代码,定义了对象的属性和方法,调用引擎来初始化一个新的对象,然后再通过额外的引擎调用设置对象的属性。这种方法的优点是,你的应用程序可以包含直接操作对象嵌入的本地方法。
在这两种情况下,如果你创建了一个对象,然后希望他一直可谓别的脚本使用的话,你必须要通过调用JS_AddRoot or JS_AddNamedRoot来固定对象。使用这些函数来确保JS引擎会保持对对象的跟踪并且在垃圾回收是清除他们,如果合适的话。、
通过脚本创建对象
通过脚本创建对象的其中一个原因在于当你只需要一个对象,它只在脚本运行过程中存在。要创建一个之持久保存于脚本调用的对象,你可以用嵌入对象代码来替代。
注意:你也可以通过脚本来创建持久保存的对象。
使用脚本创建自定义对象:
1、定义并且设定对象的细则。
2、编写定义脚本代码并且创建对象。例如: function myfun(){ var x = newObject(); . . . } 注意: 在你的应用中嵌入相应的JS引擎调用来编译并且运行脚本。你有两个选择:1、编译并且执行脚本使用一个简单的JS_EvaluateScript调用,JS_EvaluateUCScript 。2、先使用JS_CompileScript或者 JS_CompileUCScript编译脚本,然后使用JS_ExecuteScript各自执行。“UC”版本为为脚本提供Unicode编码支持。
使用脚本创建的一个对象只能在脚本的生命周期里可使用,或者可以创建持久型脚本。通常情况下,一旦脚本执行完成了,他的独显被销毁了。在很多情况下,这个行为正是你所需要的。但在有些时候,你可能想让对象在你的应用生命周期里持久化。在这些情况下你需要直接在你的应用程序中嵌入你的对象创建代码,或者你需要直接把对象关联到全局对象上,这样只要全局对象存在,它就存在。
自定义对象
应用程序可以在不需要JSClass支持下创建对象。
1、用C或C++实现自定义对象的getter、setter和方法。为每个getter和setter编写一个JSPropertyOp。为每个方法编写一个JSNative或者JSFastNative。
2、声明一个包含自定义对象的属性规格的JSFunctionSpec数组,包括getter和 setter。
3、声明一个包含自定义对象的方法规格的JSFunctionSpec数组。
4、调用
JS_NewObject
, JS_ConstructObject
, or JS_DefineObject来创建对象。
5、调用 JS_DefineProperties来定义对象的属性。
6、调用JS_DefineFunctions来定义对象的方法。
JS_SetProperty也可以用在创建对象的属性。这个属性创建是没有getter和setter的,是普通的js属性。
为对象提供私有数据
类似contexts,你可以与一个对象关联大量的数据而不需要储存数据到对象本身。调用JS_SetPrivate
指定要建立私有数据的对象,并指定指向数据的指针。
例如:
JS_SetPrivate(cx, obj, pdata);在之后要获取数据,可以调用 JS_GetPrivate,并且作为一个参数传递对象。这个函数返回对象私有数据的指针:
pdata = JS_GetPrivate(cx, obj);编译脚本
允许脚本最简单的方法就是使用JS_EvaluateScript,它将编译和运行绑定到一块去了。
但是有时候一个应用需要多次运行一个脚本。在这种情况下,编译一次执行多次要快一点。
JSAPI提供JSScript类型来代表一个编译好的脚本。JSScript的生命周期如下:
应用程序使用JS_CompileScript
, JS_CompileUTF8File
, orJS_CompileFileHandle
编译一些js代码。这些函数返回一个新的JSScript的指针。
应用调用JS_ExecuteScript
(or JS_ExecuteScriptPart
) 任意次数。在不同的contexts和不同的global对象中使用JSScript是安全的,但必须保证在创建时的runtime和线程中。
下面是一个例子:
/* * Compile a script and execute it repeatedly until an * error occurs. (If this ever returns, it returns false. * If there's no error it just keeps going.) */ JSBool compileAndRepeat(JSContext *cx, const char *filename) { JSScript *script; script = JS_CompileUTF8File(cx, JS_GetGlobalObject(cx), filename); if (script == NULL) return JS_FALSE; /* compilation error */ for (;;) { jsval result; if (!JS_ExecuteScript(cx, JS_GetGlobalObject(cx), script, &result)) break; JS_MaybeGC(cx); } return JS_FALSE; }已编译脚本的生命周期是和js对象的生命周期关联的,当脚本不再可达的时候垃圾回收器销毁脚本。JSAPI通过 JS_NewScriptObject函数来提供这一特点,使用这一特点的脚本的生命周期是这样的:
1、应用编译了一些js代码
2、为了保护已编译的脚本免受垃圾回收器的误删,应用通过调用JS_NewScriptObject创建了一个已编译对象并且使用JS_SetProperty
, JS_SetReservedSlot
, JS_AddRoot或其他函数来使得这个对象是垃圾回收可达的。
3、应用执行任意次数的已编译脚本。
4、在应用的执行中,当已编译脚本终于再也不需要了,已编译脚本将变成不可达的。
5、最终垃圾回收器回收不可达的脚本和它的其他部分。
下面是一个例子示范这个技术--注意在这个例子中不够复杂去保证JS_NewScriptObject的使用,上面的例子更加直接。
/* * Compile a script and execute it repeatedly until an * error occurs. (If this ever returns, it returns false. * If there's no error it just keeps going.) */ JSBool compileAndRepeat(JSContext *cx, const char *filename) { JSScript *script; JSObject *scriptObj; script = JS_CompileUTF8File(cx, JS_GetGlobalObject(cx), filename); if (script == NULL) return JS_FALSE; /* compilation error */ scriptObj = JS_NewScriptObject(cx, script); if (scriptObj == NULL) { JS_DestroyScript(cx, script); return JS_FALSE; } if (!JS_AddNamedObjectRoot(cx, &scriptObj, "compileAndRepeat script object")) return JS_FALSE; for (;;) { jsval result; if (!JS_ExecuteScript(cx, JS_GetGlobalObject(cx), script, &result)) break; JS_MaybeGC(cx); } JS_RemoveObjectRoot(cx, &scriptObj); /* scriptObj becomes unreachable and will eventually be collected. */ return JS_FALSE; }跟踪分析
JSAPI提供了可以便捷的实现js跟踪和分析的功能。
函数跟踪
如果你配置了启用js调用跟踪,你可以使用JS_SetFunctionCallback()来建立一个C函数,它会在js函数将要调用或者执行完成之后调用:
void funcTransition(const JSFunction *func,
const JSScript *scr,
const JSContext *const_cx,
JSBool entering)
{
JSContext *cx = const_cast<JSContext*>(const_cx);
JSString *name = JS_GetFunctionId((JSFunction*)func);
const char *entExit;
const char *nameStr;
/* build a C string for the function's name */
if (!name) {
nameStr = "Unnamed function";
} else {
nameStr = JS_EncodeString(cx, name);
}
/* build a string for whether we're entering or exiting */
if (entering) {
entExit = "Entering";
} else {
entExit = "Exiting";
}
/* output information about the trace */
printf("%s JavaScript function: %s at time: %ld", entExit, nameStr, clock());
}
void enableTracing(JSContext *cx) {
JS_SetFunctionCallback(cx, funcTransition);
}
void disableTracing(JSContext *cx) {
JS_SetFunctionCallback(cx, NULL);
}