1.ReactNative源码分析 - 概述
2.ReactNative源码分析 - JavaScriptCore C语言篇
3.ReactNative源码分析 - 启动流程
4.ReactNative源码分析 - 通信机制
5.ReactNative源码分析 - 渲染原理
一、JavaScriptCore简介
- JavaScriptCore是iOS、macOS中使用的WebKit框架中的内嵌JavaScript引擎,在iOS7开始引入系统库,命名为JavaScriptCore.framework。WebKit是开源的,感兴趣可以自行下载源码。
- JavaScriptCore主要功能是解析执行JavaScript脚本。它支持在原生环境(Objective-C、Swift、C)中执行JavaScript代码,同时支持把原生对象注入到JavaScript环境中供其使用,结合这两点JavaScriptCore就具备JS&Native交互的能力。
二、JavaScriptCore与ReactNative
- JavaScriptCore可以执行JS脚本,具备JS&Native交互的能力。ReactNative正是利用它的这一特性,在JavaScriptCore的基础上构建一座桥梁(Bridge),使得Native与JS可以高效且便捷地互相调用,这便是ReactNative框架的核心。
- ReactNative使用React做业务开发却达到了原生开发的用户体验,原理大致是:JavaScriptCore执行React模式编写的JS业务逻辑代码,通过以JavaScriptCore为基础的Bridge,JS端把执行结果传递到原生端执行原生组件渲染、调用原生端导出函数驱动所有的原生功能;原生端给予JS端反馈(回调)、或调起JS端功能……JS端收到反馈,进行新一轮计算,再次驱动原生端功能……不断循环这个过程。
- 注:V0.60.4版本开始,ReactNative集成了专们为之打造的JavaScript引擎Hermes,据官方表示:Hermes引擎使得Android平台上ReactNative应用在启动时间、内存占用、包体积都有很大程度的优化。
三、JavaScriptCore C语言版本
-
业界有很多OC版本的JavaScriptCore优秀教程(eg:深入浅出 JavaScriptCore 、打通前端与原生的桥梁:JavaScriptCore 能干哪些事情?),笔者就不废话了。
本文仅对OC/C语言版JavaScriptCore做个简单对比,并给出一个C语言版本JavaScriptCore进行Native&JS互调的例子。
OC版本的JavaScriptCore接口相对友好,学习C语言版本JavaScriptCore,建议与上述博客对比,并结合Xcode中JavaScriptCore.framework的接口文档注释来理解。
- 上图为JavaScriptCore的OC版本与C版本的主要类型对比图。OC版本是对C版本的一个面向对象封装,大部分的类型看名字就可以知晓其对于关系。JSVirtualMachine不容易看出来,不过看源码即可发现它其实就是对JSContextGroupRef的封装,两者是对应关系。
@implementation JSVirtualMachine {
JSContextGroupRef m_group;
Lock m_externalDataMutex;
NSMapTable *m_contextCache;
NSMapTable *m_externalObjectGraph;
NSMapTable *m_externalRememberedSet;
}
- (instancetype)init
{
JSContextGroupRef group = JSContextGroupCreate();
self = [self initWithContextGroupRef:group];
// The extra JSContextGroupRetain is balanced here.
JSContextGroupRelease(group);
return self;
}
四、JavaScript与Native交互
- ReactNative底层库jsi使用C++对JavaScriptCore进行了封装,并以JSCRuntime供外界使用。因此本文使用C++演示JS&Native互调。JSCRuntime为了达到隔离、通用性等目的,做了很多相对复杂的封装,但其基本原理就是以下演示的调用逻辑,可以先理解这个流程,再去看源码,避免被绕晕。
- 附上DEMO
- 值得注意的是:
关联了原生对象的JS对象,属性取值回调函数、函数调用回调函数的执行线程,与JS脚本执行的线程一致
。在主线程执行JS脚本,则回调函数执行线程是主线程;在异步线程执行JS脚本,则回调函数执行线程是异步线程。这一点关系到后面分析Native&JS交互的执行线程控制。
1.Native 调用 JS
const char *script = "var factorial = function (n) {\
if (n < 0) return;\
if (n == 0) return 1;\
return n * factorial(n-1);\
};\
var Person = function () {\
this.age = 18;\
this.sayHello = function(name) {\
return \"hello \" + name;\
}\
};\
var myName = \"fuyou\";\
var p = new Person();";
JSStringRef scriptStrRef = JSStringCreateWithUTF8CString(script);
// 创建JS Context用于执行JS脚本
JSContextGroupRef group = JSContextGroupCreate();
JSGlobalContextRef ctx = JSGlobalContextCreateInGroup(group, NULL);
JSObjectRef globalObj = JSContextGetGlobalObject(ctx);
JSEvaluateScript(ctx, scriptStrRef, NULL, NULL, 1, NULL);
JSStringRelease(scriptStrRef);
/*
1. 获取全局变量 myName
*/
JSStringRef myName = JSStringCreateWithUTF8CString("myName");
JSValueRef myNameValue = JSObjectGetProperty(ctx, globalObj, myName, NULL);
JSStringRef myNameStr = JSValueToStringCopy(ctx, myNameValue, NULL);
CFStringRef myNameCfStr = JSStringCopyCFString(kCFAllocatorSystemDefault, myNameStr);
// CFStringRef转string
const CFIndex kCStringSize = 30;
char temporaryCString[kCStringSize];
bzero(temporaryCString,kCStringSize);
CFStringGetCString(myNameCfStr, temporaryCString, kCStringSize, kCFStringEncodingUTF8);
string name(temporaryCString);
cout << "myName = " << name << endl << endl;
JSStringRelease(myName);
/*
2. 获取全局函数factorial,并执行
*/
// 获取函数
JSStringRef factorialStr = JSStringCreateWithUTF8CString("factorial");
JSValueRef factorialValue = JSObjectGetProperty(ctx, globalObj, factorialStr, NULL);
JSObjectRef factorialObj = JSValueToObject(ctx, factorialValue, NULL);
// 构造参数
JSValueRef argument = JSValueMakeNumber(ctx, 3);
JSValueRef arguments[1];
arguments[0] = argument;
// 执行函数,返回结果
JSValueRef factorialResultValue = JSObjectCallAsFunction(ctx, factorialObj, NULL, 1, arguments, NULL);
double factorialResult = JSValueToNumber(ctx, factorialResultValue, NULL);
cout << "factorialResult = " << factorialResult << endl << endl;
/*
3. 获取全局对象,并调用对象函数
*/
// 获取全局对象
JSStringRef pStr = JSStringCreateWithUTF8CString("p");
JSValueRef pStrVal = JSObjectGetProperty(ctx, globalObj, pStr,NULL);
JSStringRelease(pStr);
JSObjectRef pObj = JSValueToObject(ctx, pStrVal, NULL);
// 获取对象的sayHello函数(属性)
JSStringRef pFuncStr = JSStringCreateWithUTF8CString("sayHello");
JSValueRef pFuncVal = JSObjectGetProperty(ctx, pObj, pFuncStr, NULL);
JSStringRelease(pFuncStr);
JSObjectRef pFuncObj = JSValueToObject(ctx, pFuncVal, NULL);
// 构造函数参数
JSStringRef argStr = JSStringCreateWithUTF8CString("wuyanzu");
JSValueRef arg = JSValueMakeString(ctx, argStr);
JSStringRelease(argStr);
JSValueRef args[1];
args[0] = arg;
// 调用函数
JSValueRef pResVal = JSObjectCallAsFunction(ctx, pFuncObj, NULL, 1, args, NULL);
// 函数返回值类型转化
JSStringRef pResStr = JSValueToStringCopy(ctx, pResVal, NULL);
CFStringRef pRes = JSStringCopyCFString(kCFAllocatorSystemDefault, pResStr);
// CFStringRef转string
bzero(temporaryCString, kCStringSize);
CFStringGetCString(pRes, temporaryCString, kCStringSize, kCFStringEncodingUTF8);
string result(temporaryCString);
cout << "sayHello() = " << result << endl << endl;
JSStringRelease(myName);
JSContextGroupRelease(group);
JSGlobalContextRelease(ctx);
- 执行结果为:
myName = fuyou
factorialResult = 6
sayHello() = hello wuyanzu
2.JS 调用 Native
- 构建原生类,js属性、函数回调
// 原生C++类
class Worker {
private:
int salary = 9999;
public:
int money = 0;
void work() {
money += salary;
}
};
/*
JSObjectGetPropertyCallback:js获取属性时,会调用该函数
参数 ctx:会话; object:对象; propertyName:属性名
返回值 返回对象属性值
*/
JSValueRef getProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception)
{
Worker *w = static_cast<Worker*>(JSObjectGetPrivate(object));
return JSValueMakeNumber(ctx, w->money);
}
/*
JSObjectCallAsFunctionCallback:js调用函数是,会调用该函数
参数 ctx:会话;function:被调用的函数(函数即对象) thisObject:this对象; argumentCount:参数个数; arguments:参数值
执行js语句myObject.myFunction()是, function为myFunction对象, thisObject为调用者myObject.
返回值 返回函数调用结果
*/
JSValueRef callAsFunction(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[],JSValueRef *exception)
{
Worker *w = static_cast<Worker*>(JSObjectGetPrivate(thisObject));
w->work();
return JSValueMakeUndefined(ctx);
}
- 构建JS对象、原生对象,并把原生对象关联到JS对象
JSGlobalContextRef ctx = JSGlobalContextCreate(NULL);
JSObjectRef globalObj = JSContextGetGlobalObject(ctx);
// 定义类属性
/*
typedef struct {
const char* name; 属性名
JSObjectGetPropertyCallback getProperty; 取值回调 获取属性值时调用该函数,通过该函数返回属性值
JSObjectSetPropertyCallback setProperty; 设值回调 设置属性值时调用该函数,通过该函数设置属性值
JSPropertyAttributes attributes;
} JSStaticValue;
*/
JSStaticValue values[] = {
{"money", &getProperty, 0, kJSPropertyAttributeNone },
{ 0, 0, 0, 0}
};
// 定义类函数
/*
typedef struct {
const char* name; 函数名
JSObjectCallAsFunctionCallback callAsFunction; 函数被调用时,调用该函数,通过该函数返回调用结果
JSPropertyAttributes attributes;
} JSStaticFunction;
*/
JSStaticFunction functions[] = {
{"work", &callAsFunction, kJSPropertyAttributeNone },
{ 0, 0, 0 }
};
// 定义类
JSClassDefinition classDef = kJSClassDefinitionEmpty;
classDef.version = 0;
classDef.attributes = kJSClassAttributeNone;
classDef.className = "Worker";
classDef.parentClass = 0;
classDef.staticValues = values;
classDef.staticFunctions = functions;
// 创建一个 JavaScript Worker类
JSClassRef t = JSClassCreate(&classDef);
// 新建一个JavaScript类对象,并使之绑定原生Worker对象w
Worker w;
JSObjectRef classObj = JSObjectMake(ctx,t, &w);
// 将新建的对象注入JavaScript中
JSStringRef objName = JSStringCreateWithUTF8CString("w");
JSObjectSetProperty(ctx, globalObj, objName, classObj, kJSPropertyAttributeNone, NULL);
// 执行js代码,“两次”调用w对象work()函数
JSStringRef workScript = JSStringCreateWithUTF8CString("w.work()");
JSEvaluateScript(ctx, workScript, classObj, NULL, 1, NULL);
JSEvaluateScript(ctx, workScript, classObj, NULL, 1, NULL);
JSStringRelease(workScript);
// 执行js代码,获取w对象money属性
JSStringRef getMoneyscript = JSStringCreateWithUTF8CString("var money = w.money;");
JSEvaluateScript(ctx, getMoneyscript, classObj, NULL, 1, NULL);
JSStringRelease(getMoneyscript);
// 对比原生对象、js对象 属性值
JSStringRef moneyRef = JSStringCreateWithUTF8CString("money");
JSValueRef moneyValue = JSObjectGetProperty(ctx, globalObj, moneyRef, NULL);
JSStringRelease(moneyRef);
double money = JSValueToNumber(ctx, moneyValue, NULL);
cout << endl << "JS环境中, w.money = " << money << endl;
cout << "原生环境中,w.money = " << w.money << endl << endl;
JSGlobalContextRelease(ctx);
- 执行结果如下
JS环境中, w.money = 19998
原生环境中,w.money = 19998