ReactNative源碼分析 - JavaScriptCore C語言篇

1.ReactNative源碼分析 - 概述
2.ReactNative源碼分析 - JavaScriptCore C語言篇
3.ReactNative源碼分析 - 啟動(dòng)流程
4.ReactNative源碼分析 - 通信機(jī)制
5.ReactNative源碼分析 - 渲染原理

一、JavaScriptCore簡介

  • JavaScriptCore是iOS、macOS中使用的WebKit框架中的內(nèi)嵌JavaScript引擎,在iOS7開始引入系統(tǒng)庫,命名為JavaScriptCore.framework。WebKit是開源的,感興趣可以自行下載源碼。
  • JavaScriptCore主要功能是解析執(zhí)行JavaScript腳本。它支持在原生環(huán)境(Objective-C、Swift、C)中執(zhí)行JavaScript代碼,同時(shí)支持把原生對(duì)象注入到JavaScript環(huán)境中供其使用,結(jié)合這兩點(diǎn)JavaScriptCore就具備JS&Native交互的能力。

二、JavaScriptCore與ReactNative

  • JavaScriptCore可以執(zhí)行JS腳本,具備JS&Native交互的能力。ReactNative正是利用它的這一特性,在JavaScriptCore的基礎(chǔ)上構(gòu)建一座橋梁(Bridge),使得Native與JS可以高效且便捷地互相調(diào)用,這便是ReactNative框架的核心。
  • ReactNative使用React做業(yè)務(wù)開發(fā)卻達(dá)到了原生開發(fā)的用戶體驗(yàn),原理大致是:JavaScriptCore執(zhí)行React模式編寫的JS業(yè)務(wù)邏輯代碼,通過以JavaScriptCore為基礎(chǔ)的Bridge,JS端把執(zhí)行結(jié)果傳遞到原生端執(zhí)行原生組件渲染、調(diào)用原生端導(dǎo)出函數(shù)驅(qū)動(dòng)所有的原生功能;原生端給予JS端反饋(回調(diào))、或調(diào)起JS端功能……JS端收到反饋,進(jìn)行新一輪計(jì)算,再次驅(qū)動(dòng)原生端功能……不斷循環(huán)這個(gè)過程。
  • 注:V0.60.4版本開始,ReactNative集成了專們?yōu)橹蛟斓腏avaScript引擎Hermes,據(jù)官方表示:Hermes引擎使得Android平臺(tái)上ReactNative應(yīng)用在啟動(dòng)時(shí)間、內(nèi)存占用、包體積都有很大程度的優(yōu)化。

三、JavaScriptCore C語言版本

  • 業(yè)界有很多OC版本的JavaScriptCore優(yōu)秀教程(eg:深入淺出 JavaScriptCore打通前端與原生的橋梁:JavaScriptCore 能干哪些事情?),筆者就不廢話了。

    本文僅對(duì)OC/C語言版JavaScriptCore做個(gè)簡單對(duì)比,并給出一個(gè)C語言版本JavaScriptCore進(jìn)行Native&JS互調(diào)的例子。

    OC版本的JavaScriptCore接口相對(duì)友好,學(xué)習(xí)C語言版本JavaScriptCore,建議與上述博客對(duì)比,并結(jié)合Xcode中JavaScriptCore.framework的接口文檔注釋來理解。

OC&C語言版本JavaScript主要類型對(duì)比圖.jpg
  • 上圖為JavaScriptCore的OC版本與C版本的主要類型對(duì)比圖。OC版本是對(duì)C版本的一個(gè)面向?qū)ο蠓庋b,大部分的類型看名字就可以知曉其對(duì)于關(guān)系。JSVirtualMachine不容易看出來,不過看源碼即可發(fā)現(xiàn)它其實(shí)就是對(duì)JSContextGroupRef的封裝,兩者是對(duì)應(yīng)關(guān)系。
@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++對(duì)JavaScriptCore進(jìn)行了封裝,并以JSCRuntime供外界使用。因此本文使用C++演示JS&Native互調(diào)。JSCRuntime為了達(dá)到隔離、通用性等目的,做了很多相對(duì)復(fù)雜的封裝,但其基本原理就是以下演示的調(diào)用邏輯,可以先理解這個(gè)流程,再去看源碼,避免被繞暈。
  • 附上DEMO
  • 值得注意的是:關(guān)聯(lián)了原生對(duì)象的JS對(duì)象,屬性取值回調(diào)函數(shù)、函數(shù)調(diào)用回調(diào)函數(shù)的執(zhí)行線程,與JS腳本執(zhí)行的線程一致。在主線程執(zhí)行JS腳本,則回調(diào)函數(shù)執(zhí)行線程是主線程;在異步線程執(zhí)行JS腳本,則回調(diào)函數(shù)執(zhí)行線程是異步線程。這一點(diǎn)關(guān)系到后面分析Native&JS交互的執(zhí)行線程控制。

1.Native 調(diào)用 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);
    
    // 創(chuàng)建JS Context用于執(zhí)行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轉(zhuǎn)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. 獲取全局函數(shù)factorial,并執(zhí)行
     */
    // 獲取函數(shù)
    JSStringRef factorialStr = JSStringCreateWithUTF8CString("factorial");
    JSValueRef factorialValue = JSObjectGetProperty(ctx, globalObj, factorialStr, NULL);
    JSObjectRef factorialObj = JSValueToObject(ctx, factorialValue, NULL);
    // 構(gòu)造參數(shù)
    JSValueRef argument = JSValueMakeNumber(ctx, 3);
    JSValueRef arguments[1];
    arguments[0] = argument;
    // 執(zhí)行函數(shù),返回結(jié)果
    JSValueRef factorialResultValue = JSObjectCallAsFunction(ctx, factorialObj, NULL, 1, arguments, NULL);
    double factorialResult = JSValueToNumber(ctx, factorialResultValue, NULL);
    cout << "factorialResult = " << factorialResult << endl << endl;
    
    /*
     3. 獲取全局對(duì)象,并調(diào)用對(duì)象函數(shù)
     */
    // 獲取全局對(duì)象
    JSStringRef pStr = JSStringCreateWithUTF8CString("p");
    JSValueRef pStrVal = JSObjectGetProperty(ctx, globalObj, pStr,NULL);
    JSStringRelease(pStr);
    JSObjectRef pObj = JSValueToObject(ctx, pStrVal, NULL);
    // 獲取對(duì)象的sayHello函數(shù)(屬性)
    JSStringRef pFuncStr = JSStringCreateWithUTF8CString("sayHello");
    JSValueRef pFuncVal =  JSObjectGetProperty(ctx, pObj, pFuncStr, NULL);
    JSStringRelease(pFuncStr);
    JSObjectRef pFuncObj = JSValueToObject(ctx, pFuncVal, NULL);
    // 構(gòu)造函數(shù)參數(shù)
    JSStringRef argStr = JSStringCreateWithUTF8CString("wuyanzu");
    JSValueRef arg = JSValueMakeString(ctx, argStr);
    JSStringRelease(argStr);
    JSValueRef args[1];
    args[0] = arg;
    // 調(diào)用函數(shù)
    JSValueRef pResVal = JSObjectCallAsFunction(ctx, pFuncObj, NULL, 1, args, NULL);
    // 函數(shù)返回值類型轉(zhuǎn)化
    JSStringRef pResStr = JSValueToStringCopy(ctx, pResVal, NULL);
    CFStringRef pRes = JSStringCopyCFString(kCFAllocatorSystemDefault, pResStr);
    // CFStringRef轉(zhuǎn)string
    bzero(temporaryCString, kCStringSize);
    CFStringGetCString(pRes, temporaryCString, kCStringSize, kCFStringEncodingUTF8);
    string result(temporaryCString);
    cout << "sayHello() = " << result << endl << endl;
    JSStringRelease(myName);
    
    JSContextGroupRelease(group);
    JSGlobalContextRelease(ctx);
  • 執(zhí)行結(jié)果為:
myName = fuyou
factorialResult = 6
sayHello() = hello wuyanzu

2.JS 調(diào)用 Native

  • 構(gòu)建原生類,js屬性、函數(shù)回調(diào)
// 原生C++類
class Worker {
private:
    int salary = 9999;
public:
    int money = 0;
    void work() {
        money += salary;
    }
};

/*
 JSObjectGetPropertyCallback:js獲取屬性時(shí),會(huì)調(diào)用該函數(shù)
 參數(shù)  ctx:會(huì)話; object:對(duì)象; propertyName:屬性名
 返回值 返回對(duì)象屬性值
 */
JSValueRef getProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception)
{
    Worker *w = static_cast<Worker*>(JSObjectGetPrivate(object));
    return JSValueMakeNumber(ctx, w->money);
}

/*
 JSObjectCallAsFunctionCallback:js調(diào)用函數(shù)是,會(huì)調(diào)用該函數(shù)
 參數(shù) ctx:會(huì)話;function:被調(diào)用的函數(shù)(函數(shù)即對(duì)象) thisObject:this對(duì)象; argumentCount:參數(shù)個(gè)數(shù); arguments:參數(shù)值
 執(zhí)行js語句myObject.myFunction()是, function為myFunction對(duì)象, thisObject為調(diào)用者myObject.
 返回值 返回函數(shù)調(diào)用結(jié)果
 */
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);
}
  • 構(gòu)建JS對(duì)象、原生對(duì)象,并把原生對(duì)象關(guān)聯(lián)到JS對(duì)象
    JSGlobalContextRef ctx = JSGlobalContextCreate(NULL);
    JSObjectRef globalObj = JSContextGetGlobalObject(ctx);
    
    // 定義類屬性
    /*
     typedef struct {
         const char* name;  屬性名
         JSObjectGetPropertyCallback getProperty; 取值回調(diào) 獲取屬性值時(shí)調(diào)用該函數(shù),通過該函數(shù)返回屬性值
         JSObjectSetPropertyCallback setProperty; 設(shè)值回調(diào) 設(shè)置屬性值時(shí)調(diào)用該函數(shù),通過該函數(shù)設(shè)置屬性值
         JSPropertyAttributes attributes;
     } JSStaticValue;
     */
    JSStaticValue values[] = {
        {"money", &getProperty, 0, kJSPropertyAttributeNone },
        { 0, 0, 0, 0}
    };
    
    // 定義類函數(shù)
    /*
     typedef struct {
         const char* name; 函數(shù)名
         JSObjectCallAsFunctionCallback callAsFunction; 函數(shù)被調(diào)用時(shí),調(diào)用該函數(shù),通過該函數(shù)返回調(diào)用結(jié)果
         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;
    
    // 創(chuàng)建一個(gè) JavaScript Worker類
    JSClassRef t = JSClassCreate(&classDef);
    // 新建一個(gè)JavaScript類對(duì)象,并使之綁定原生Worker對(duì)象w
    Worker w;
    JSObjectRef classObj = JSObjectMake(ctx,t, &w);
    
    // 將新建的對(duì)象注入JavaScript中
    JSStringRef objName = JSStringCreateWithUTF8CString("w");
    JSObjectSetProperty(ctx, globalObj, objName, classObj, kJSPropertyAttributeNone, NULL);
    // 執(zhí)行js代碼,“兩次”調(diào)用w對(duì)象work()函數(shù)
    JSStringRef workScript = JSStringCreateWithUTF8CString("w.work()");
    JSEvaluateScript(ctx, workScript, classObj, NULL, 1, NULL);
    JSEvaluateScript(ctx, workScript, classObj, NULL, 1, NULL);
    JSStringRelease(workScript);
    // 執(zhí)行js代碼,獲取w對(duì)象money屬性
    JSStringRef getMoneyscript = JSStringCreateWithUTF8CString("var money = w.money;");
    JSEvaluateScript(ctx, getMoneyscript, classObj, NULL, 1, NULL);
    JSStringRelease(getMoneyscript);
    
    // 對(duì)比原生對(duì)象、js對(duì)象 屬性值
    JSStringRef moneyRef = JSStringCreateWithUTF8CString("money");
    JSValueRef moneyValue = JSObjectGetProperty(ctx, globalObj, moneyRef, NULL);
    JSStringRelease(moneyRef);
    double money = JSValueToNumber(ctx, moneyValue, NULL);
    cout << endl << "JS環(huán)境中, w.money = " << money << endl;
    cout << "原生環(huán)境中,w.money = " << w.money << endl << endl;
    
    JSGlobalContextRelease(ctx);
  • 執(zhí)行結(jié)果如下
JS環(huán)境中, w.money = 19998
原生環(huán)境中,w.money = 19998

Reference

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容