從時間線來看骑脱、前端交互分為三個階段只锻。
本帖不是用法教學貼
主要針對JavaScriptCore工作原理進行探究
首先傅是、借助一套JSC開源源碼(地址)团驱。我們可以更直觀的解析JSC。
JS調(diào)用OC
一個最簡單的注冊調(diào)用
- oc
- (void)configMyJSCore {
//設(shè)置js環(huán)境
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
self.context = context;
self.context[@"Log"] = ^(NSString *msg){
NSLog(@"%@",msg);
};
}
- js
<body>
<h1>WebViewJavascriptBridge Demo</h1>
<button type="button" onclick="JSCallOC1()">JS調(diào)用OC方法1</button>
</body>
<script type="text/javascript">
function JSCallOC1 () {
Log('JSCallOC1');
}
</script>
點擊button打印:
2018-01-10 15:18:47.687046+0800 test[10151:397569] JSCallOC1
那么丛塌、一行一行看唄较解。
self.context[@"Log"] = ^(NSString *msg){
NSLog(@"%@",msg);
};
JSContext是啥。字典么赴邻?肯定不是哨坪。
但是在JSContext.h文件中。有這樣兩個方法乍楚。
@interface JSContext (SubscriptSupport)
/*!
@method
@abstract Get a particular property on the global object.
@result The JSValue for the global object's property.
*/
- (JSValue *)objectForKeyedSubscript:(id)key;
/*!
@method
@abstract Set a particular property on the global object.
*/
- (void)setObject:(id)object forKeyedSubscript:(NSObject <NSCopying> *)key;
@end
在全局對象上設(shè)置一個屬性、以及獲得一個屬性届慈⊥较看著有點意思、把上面的代碼換成這個寫法試試金顿。
[self.context setObject:^(NSString *msg){
NSLog(@"%@",msg);
} forKeyedSubscript:@"Log"];
一切正常臊泌。于是、源碼出場揍拆。
- (JSValue *)objectForKeyedSubscript:(id)key
{
return [self globalObject][key];
}
- (void)setObject:(id)object forKeyedSubscript:(NSObject <NSCopying> *)key
{
[self globalObject][key] = object;
}
里面對[self globalObject]對象的某屬性進行了一次賦值/取值渠概。
- globalObject是什么呢
從文檔可以看出、是window對象(如果沒有window也代表一個作用域)嫂拴。
源碼中
- (JSValue *)globalObject
{
return [JSValue valueWithJSValueRef:JSContextGetGlobalObject(m_context) inContext:self];
}
/*!
@method
@abstract Creates a JSValue, wrapping its C API counterpart.
@param value
@param context
@result The Objective-C API equivalent of the specified JSValueRef.
*/
+ (JSValue *)valueWithJSValueRef:(JSValueRef)value inContext:(JSContext *)context;
該方法在某個Context作用域中播揪、查詢并返回一個包裝成JSValue的JS對象。
這里筒狠、就是在self.context的作用于中猪狈、查找并返回了一個GlobalObject對象。
具體一點辩恼、在oc和js中分別打庸兔怼:
//oc中
NSLog(@"oc---%@",[self.context globalObject]);
//js中
Log('js---'+window);
結(jié)果:
test[10409:454602] oc---[object Window]
test[10409:454752] js---[object Window]
進一步確認是不是同一個window?
//oc中
JSValue * document = [[self.context globalObject]objectForKeyedSubscript:@"document"];
NSString * title = [[document objectForKeyedSubscript:@"title"] toString];
NSLog(@"oc---%@",title);
//js中
Log('js---'+window.document.title);
結(jié)果:
test[10473:462274] oc---頁面title
test[10473:462411] js---頁面title
一模一樣谓形。
所以。剛才提到的:
里面對[self globalObject]對象的某屬性進行了一次賦值/取值疆前。
實際上就是對html的window對象的某屬性進行了賦值/取值寒跳。
而H5中window對象的屬性是什么、就是頂層變量竹椒、也叫全局變量童太。
window.property = '頂層變量';
alert(property);
再深入一些、這個屬性如何被添加的呢碾牌?
***JSValue.h***
- (void)setValue:(id)value forProperty:(NSString *)property;
***JSValue.m***
***
* Sets the value of the named property in the JavaScript object value.
* Calling this method is equivalent to using the subscript operator with a string subscript in JavaScript. Use it to set or create fields or properties in JavaScript objects.
Parameters
***
- (void)setValue:(id)value forProperty:(NSString *)propertyName
{
JSValueRef exception = 0;
JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
if (exception) {
[_context notifyException:exception];
return;
}
//通過nsstr創(chuàng)建一個jsstr
JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName);
JSObjectSetProperty([_context JSGlobalContextRef], object, name, objectToValue(_context, value), 0, &exception);
JSStringRelease(name);
if (exception) {
[_context notifyException:exception];
return;
}
}
方法的解釋大概翻譯是
調(diào)用此方法相當于在JavaScript中使用子腳本操作符康愤。使用它來設(shè)置或創(chuàng)建JavaScript對象中的字段或?qū)傩浴?
再具體一點:
void JSObjectSetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSPropertyAttributes attributes, JSValueRef* exception)
{
if (!ctx) {
ASSERT_NOT_REACHED();
return;
}
ExecState* exec = toJS(ctx);
APIEntryShim entryShim(exec);
JSObject* jsObject = toJS(object);
Identifier name(propertyName->identifier(&exec->vm()));
//將oc綁定的方法轉(zhuǎn)化成js對象
JSValue jsValue = toJS(exec, value);
if (attributes && !jsObject->hasProperty(exec, name)) {
PropertyDescriptor desc(jsValue, attributes);
jsObject->methodTable()->defineOwnProperty(jsObject, exec, name, desc, false);
} else {
PutPropertySlot slot(jsObject);
//向被注入的對象的methodTable中存入該方法、說明舶吗、名字等參數(shù)征冷。
jsObject->methodTable()->put(jsObject, exec, name, jsValue, slot);
}
if (exec->hadException()) {
if (exception)
*exception = toRef(exec, exec->exception());
exec->clearException();
}
}
將oc注冊的方法、轉(zhuǎn)化成js對象后誓琼、存入被注入對象的方法table中(忽然感覺很想runtime的機制了~)检激。
OC調(diào)用JS
本質(zhì)上都是這個方法:
JSValue *value1 = [self.jsContext evaluateScript:@"hello()"];
***JSContext.h***
/*!
@methodgroup Evaluating Scripts
*/
/*!
@method
@abstract Evaluate a string of JavaScript code.
@param script A string containing the JavaScript code to evaluate.
@result The last value generated by the script.
*/
- (JSValue *)evaluateScript:(NSString *)script;
作用:嘗試執(zhí)行一段js代碼。
在具體一點的代碼:
***JSBase.cpp***
JSValueRef JSEvaluateScript(JSContextRef ctx, JSStringRef script, JSObjectRef thisObject, JSStringRef sourceURL, int startingLineNumber, JSValueRef* exception)
{
if (!ctx) {
ASSERT_NOT_REACHED();
return 0;
}
ExecState* exec = toJS(ctx);
APIEntryShim entryShim(exec);
JSObject* jsThisObject = toJS(thisObject);
// evaluate sets "this" to the global object if it is NULL
JSGlobalObject* globalObject = exec->vmEntryGlobalObject();
SourceCode source = makeSource(script->string(), sourceURL->string(), TextPosition(OrdinalNumber::fromOneBasedInt(startingLineNumber), OrdinalNumber::first()));
JSValue evaluationException;
JSValue returnValue = evaluate(globalObject->globalExec(), source, jsThisObject, &evaluationException);
if (evaluationException) {
if (exception)
*exception = toRef(exec, evaluationException);
return 0;
}
if (returnValue)
return toRef(exec, returnValue);
// happens, for example, when the only statement is an empty (';') statement
return toRef(exec, jsUndefined());
}
***completion.cpp***
JSValue evaluate(ExecState* exec, const SourceCode& source, JSValue thisValue, JSValue* returnedException)
{
JSLockHolder lock(exec);
RELEASE_ASSERT(exec->vm().identifierTable == wtfThreadData().currentIdentifierTable());
RELEASE_ASSERT(!exec->vm().isCollectorBusy());
CodeProfiling profile(source);
ProgramExecutable* program = ProgramExecutable::create(exec, source);
if (!program) {
if (returnedException)
*returnedException = exec->vm().exception();
exec->vm().clearException();
return jsUndefined();
}
if (!thisValue || thisValue.isUndefinedOrNull())
thisValue = exec->vmEntryGlobalObject();
JSObject* thisObj = jsCast<JSObject*>(thisValue.toThis(exec, NotStrictMode));
JSValue result = exec->interpreter()->execute(program, exec, thisObj);
if (exec->hadException()) {
if (returnedException)
*returnedException = exec->exception();
exec->clearException();
return jsUndefined();
}
RELEASE_ASSERT(result);
return result;
}
}
講道理翻到這我已經(jīng)沒啥耐心逐字逐句的擼了~畢竟這個開源包是不能運行的腹侣。
簡單概括一下:
- 通過上下文獲取JS全局對象叔收。
- 將全局對象、JS代碼(轉(zhuǎn)換成JsStr之后)等傲隶。交由虛擬機進行搜索并獲得上下文中可執(zhí)行列表饺律。
- 執(zhí)行可執(zhí)行列表獲得返回值。(具體的執(zhí)行代碼可以自己去源碼里挑戰(zhàn)一下)
由于源碼的包直接從開源的WebKit中獲取跺株。缺少項目文件复濒、無法進行編譯。
所以并不保證這篇文章具有100%的正確性乒省。
只是因為網(wǎng)上沒找到源碼解析相關(guān)的帖子巧颈、又很好奇。只能自己動手袖扛。
如有錯誤砸泛、還望指正。
最后
本文主要是自己的學習與總結(jié)蛆封。如果文內(nèi)存在紕漏唇礁、萬望留言斧正。如果不吝賜教小弟更加感謝娶吞。