JavaScriptCore全面解析 (上篇 轉(zhuǎn)載于殷源的專欄訂閱)

一、JavaScript

1. JavaScript干啥的?

2. JavaScript起源與歷史

3. JavaScript與ECMAScript

4. Java和JavaScript

二、 JavaScriptCore

1. 瀏覽器演進(jìn)

2. WebKit排版引擎

3. JavaScript引擎

4. JavaScriptCore組成

5. JavaScriptCore

6. Hello World!

三、 JSVirtualMachine

四、 JSContext

1. JSContext執(zhí)行JS代碼

2. JSContext訪問(wèn)JS對(duì)象

五、 JSValue

1. JSValue類型轉(zhuǎn)換

2. NSDictionary與JS對(duì)象

3. NSArray與JS數(shù)組

4. Block/函數(shù)和JS function

5. OC對(duì)象和JS對(duì)象

JavaScript越來(lái)越多地出現(xiàn)在我們客戶端開(kāi)發(fā)的視野中,從ReactNative到JSpatch,JavaScript與客戶端相結(jié)合的技術(shù)開(kāi)始變得魅力無(wú)窮。本文主要講解iOS中的JavaScriptCore框架,正是它為iOS提供了執(zhí)行JavaScript代碼的能力。未來(lái)的技術(shù)日新月異,JavaScript與iOS正在碰撞出新的激情。

JavaScriptCore是JavaScript的虛擬機(jī),為JavaScript的執(zhí)行提供底層資源。

一、JavaScript

在討論JavaScriptCore之前,我們首先必須對(duì)JavaScript有所了解。

1. JavaScript干啥的?

說(shuō)的高大上一點(diǎn):一門基于原型、函數(shù)先行的高級(jí)編程語(yǔ)言,通過(guò)解釋執(zhí)行,是動(dòng)態(tài)類型的直譯語(yǔ)言。是一門多范式的語(yǔ)言,它支持面向?qū)ο缶幊?,命令式編程,以及函?shù)式編程。

說(shuō)的通俗一點(diǎn):主要用于網(wǎng)頁(yè),為其提供動(dòng)態(tài)交互的能力??汕度雱?dòng)態(tài)文本于HTML頁(yè)面,對(duì)瀏覽器事件作出響應(yīng),讀寫(xiě)HTML元素,控制cookies等。

再通俗一點(diǎn):搶月餅,button.click()。(PS:請(qǐng)謹(jǐn)慎使用while循環(huán))

2. JavaScript起源與歷史

1990年底,歐洲核能研究組織(CERN)科學(xué)家Tim Berners-Lee,在互聯(lián)網(wǎng)的基礎(chǔ)上,發(fā)明了萬(wàn)維網(wǎng)(World Wide Web),從此可以在網(wǎng)上瀏覽網(wǎng)頁(yè)文件。

1994年12月,Netscape 發(fā)布了一款面向普通用戶的新一代的瀏覽器Navigator 1.0版,市場(chǎng)份額一舉超過(guò)90%。

1995年,Netscape公司雇傭了程序員Brendan Eich開(kāi)發(fā)這種嵌入網(wǎng)頁(yè)的腳本語(yǔ)言。最初名字叫做Mocha,1995年9月改為L(zhǎng)iveScript。

1995年12月,Netscape公司與Sun公司達(dá)成協(xié)議,后者允許將這種語(yǔ)言叫做JavaScript。

3. JavaScript與ECMAScript

“JavaScript”是Sun公司的注冊(cè)商標(biāo),用來(lái)特制網(wǎng)景(現(xiàn)在的Mozilla)對(duì)于這門語(yǔ)言的實(shí)現(xiàn)。網(wǎng)景將這門語(yǔ)言作為標(biāo)準(zhǔn)提交給了ECMA——?dú)W洲計(jì)算機(jī)制造協(xié)會(huì)。由于商標(biāo)上的沖突,這門語(yǔ)言的標(biāo)準(zhǔn)版本改了一個(gè)丑陋的名字“ECMAScript”。同樣由于商標(biāo)的沖突,微軟對(duì)這門語(yǔ)言的實(shí)現(xiàn)版本取了一個(gè)廣為人知的名字“Jscript”。

ECMAScript作為JavaScript的標(biāo)準(zhǔn),一般認(rèn)為后者是前者的實(shí)現(xiàn)。

4. Java和JavaScript

《雷鋒和雷峰塔》

Java 和 JavaScript 是兩門不同的編程語(yǔ)言

一般認(rèn)為,當(dāng)時(shí) Netscape 之所以將 LiveScript 命名為 JavaScript,是因?yàn)?Java 是當(dāng)時(shí)最流行的編程語(yǔ)言,帶有 “Java” 的名字有助于這門新生語(yǔ)言的傳播。

二、 JavaScriptCore

1. 瀏覽器演進(jìn)

演進(jìn)完整圖

https://upload.wikimedia.org/wikipedia/commons/7/74/Timeline_of_web_browsers.svg

WebKit分支

現(xiàn)在使用WebKit的主要兩個(gè)瀏覽器Sfari和Chromium(Chorme的開(kāi)源項(xiàng)目)。WebKit起源于KDE的開(kāi)源項(xiàng)目Konqueror的分支,由蘋(píng)果公司用于Sfari瀏覽器。其一條分支發(fā)展成為Chorme的內(nèi)核,2013年Google在此基礎(chǔ)上開(kāi)發(fā)了新的Blink內(nèi)核。

2. WebKit排版引擎

webkit是sfari、chrome等瀏覽器的排版引擎,各部分架構(gòu)圖如下

webkit Embedding API是browser UI與webpage進(jìn)行交互的api接口;

platformAPI提供與底層驅(qū)動(dòng)的交互, 如網(wǎng)絡(luò), 字體渲染, 影音文件解碼, 渲染引擎等;

WebCore它實(shí)現(xiàn)了對(duì)文檔的模型化,包括了CSS, DOM, Render等的實(shí)現(xiàn);

JSCore是專門處理JavaScript腳本的引擎;

3. JavaScript引擎

JavaScript引擎是專門處理JavaScript腳本的虛擬機(jī),一般會(huì)附帶在網(wǎng)頁(yè)瀏覽器之中。第一個(gè)JavaScript引擎由布蘭登·艾克在網(wǎng)景公司開(kāi)發(fā),用于Netscape Navigator網(wǎng)頁(yè)瀏覽器中。JavaScriptCore就是一個(gè)JavaScript引擎。

下圖是當(dāng)前主要的還在開(kāi)發(fā)中的JavaScript引擎

4. JavaScriptCore組成

JavaScriptCore主要由以下模塊組成:

Lexer 詞法分析器,將腳本源碼分解成一系列的Token

Parser 語(yǔ)法分析器,處理Token并生成相應(yīng)的語(yǔ)法樹(shù)

LLInt 低級(jí)解釋器,執(zhí)行Parser生成的二進(jìn)制代碼

Baseline JIT 基線JIT(just in time 實(shí)施編譯)

DFG 低延遲優(yōu)化的JIT

FTL 高通量?jī)?yōu)化的JIT

關(guān)于更多JavaScriptCore的實(shí)現(xiàn)細(xì)節(jié),參考https://trac.webkit.org/wiki/JavaScriptCore

5. JavaScriptCore

JavaScriptCore是一個(gè)C++實(shí)現(xiàn)的開(kāi)源項(xiàng)目。使用Apple提供的JavaScriptCore框架,你可以在Objective-C或者基于C的程序中執(zhí)行Javascript代碼,也可以向JavaScript環(huán)境中插入一些自定義的對(duì)象。JavaScriptCore從iOS 7.0之后可以直接使用。

在JavaScriptCore.h中,我們可以看到這個(gè)

#ifndef JavaScriptCore_h#define JavaScriptCore_h#include#include#ifdefined(__OBJC__)&&JSC_OBJC_API_ENABLED#import"JSContext.h"#import"JSValue.h"#import"JSManagedValue.h"#import"JSVirtualMachine.h"#import"JSExport.h"#endif#endif/* JavaScriptCore_h */

這里已經(jīng)很清晰地列出了JavaScriptCore的主要幾個(gè)類:

JSContext

JSValue

JSManagedValue

JSVirtualMachine

JSExport

接下來(lái)我們會(huì)依次講解這幾個(gè)類的用法。

6. Hello World!

這段代碼展示了如何在Objective-C中執(zhí)行一段JavaScript代碼,并且獲取返回值并轉(zhuǎn)換成OC數(shù)據(jù)打印

//創(chuàng)建虛擬機(jī)JSVirtualMachine*vm=[[JSVirtualMachine alloc]init];//創(chuàng)建上下文JSContext*context=[[JSContext alloc]initWithVirtualMachine:vm];//執(zhí)行JavaScript代碼并獲取返回值JSValue*value=[context evaluateScript:@"1+2*3"];//轉(zhuǎn)換成OC數(shù)據(jù)并打印NSLog(@"value = %d",[value toInt32]);Outputvalue=7

三、 JSVirtualMachine

一個(gè)JSVirtualMachine的實(shí)例就是一個(gè)完整獨(dú)立的JavaScript的執(zhí)行環(huán)境,為JavaScript的執(zhí)行提供底層資源。

這個(gè)類主要用來(lái)做兩件事情:

實(shí)現(xiàn)并發(fā)的JavaScript執(zhí)行

JavaScript和Objective-C橋接對(duì)象的內(nèi)存管理

看下頭文件SVirtualMachine.h里有什么:

NS_CLASS_AVAILABLE(10_9,7_0)@interfaceJSVirtualMachine:NSObject/* 創(chuàng)建一個(gè)新的完全獨(dú)立的虛擬機(jī) */(instancetype)init;/* 對(duì)橋接對(duì)象進(jìn)行內(nèi)存管理 */-(void)addManagedReference:(id)object withOwner:(id)owner;/* 取消對(duì)橋接對(duì)象的內(nèi)存管理 */-(void)removeManagedReference:(id)object withOwner:(id)owner;@end

每一個(gè)JavaScript上下文(JSContext對(duì)象)都?xì)w屬于一個(gè)虛擬機(jī)(JSVirtualMachine)。每個(gè)虛擬機(jī)可以包含多個(gè)不同的上下文,并允許在這些不同的上下文之間傳值(JSValue對(duì)象)。

然而,每個(gè)虛擬機(jī)都是完整且獨(dú)立的,有其獨(dú)立的堆空間和垃圾回收器(garbage collector ),GC無(wú)法處理別的虛擬機(jī)堆中的對(duì)象,因此你不能把一個(gè)虛擬機(jī)中創(chuàng)建的值傳給另一個(gè)虛擬機(jī)。

線程和JavaScript的并發(fā)執(zhí)行

JavaScriptCore API都是線程安全的。你可以在任意線程創(chuàng)建JSValue或者執(zhí)行JS代碼,然而,所有其他想要使用該虛擬機(jī)的線程都要等待。

如果想并發(fā)執(zhí)行JS,需要使用多個(gè)不同的虛擬機(jī)來(lái)實(shí)現(xiàn)。

可以在子線程中執(zhí)行JS代碼。

通過(guò)下面這個(gè)demo來(lái)理解一下這個(gè)并發(fā)機(jī)制

JSContext*context=[[CustomJSContext alloc]init];JSContext*context1=[[CustomJSContext alloc]init];JSContext*context2=[[CustomJSContext alloc]initWithVirtualMachine:[context virtualMachine]];NSLog(@"start");dispatch_async(queue,^{while(true){sleep(1);[context evaluateScript:@"log('tick')"];}});dispatch_async(queue1,^{while(true){sleep(1);[context1 evaluateScript:@"log('tick_1')"];}});dispatch_async(queue2,^{while(true){sleep(1);[context2 evaluateScript:@"log('tick_2')"];}});[context evaluateScript:@"sleep(5)"];NSLog(@"end");

context和context2屬于同一個(gè)虛擬機(jī)。

context1屬于另一個(gè)虛擬機(jī)。

三個(gè)線程分別異步執(zhí)行每秒1次的js log,首先會(huì)休眠1秒。

在context上執(zhí)行一個(gè)休眠5秒的JS函數(shù)。

首先執(zhí)行的應(yīng)該是休眠5秒的JS函數(shù),在此期間,context所處的虛擬機(jī)上的其他調(diào)用都會(huì)處于等待狀態(tài),因此tick和tick_2在前5秒都不會(huì)有執(zhí)行。

而context1所處的虛擬機(jī)仍然可以正常執(zhí)行tick_1。

休眠5秒結(jié)束后,tick和tick_2才會(huì)開(kāi)始執(zhí)行(不保證先后順序)。

實(shí)際運(yùn)行輸出的log是:

start

tick_1

tick_1

tick_1

tick_1

end

tick

tick_2

四、 JSContext

一個(gè)JSContext對(duì)象代表一個(gè)JavaScript執(zhí)行環(huán)境。在native代碼中,使用JSContext去執(zhí)行JS代碼,訪問(wèn)JS中定義或者計(jì)算的值,并使JavaScript可以訪問(wèn)native的對(duì)象、方法、函數(shù)。

1. JSContext執(zhí)行JS代碼

調(diào)用evaluateScript函數(shù)可以執(zhí)行一段top-level的JS代碼,并可向global對(duì)象添加函數(shù)和對(duì)象定義

其返回值是JavaScript代碼中最后一個(gè)生成的值

API Reference

NS_CLASS_AVAILABLE(10_9,7_0)@interfaceJSContext:NSObject/* 創(chuàng)建一個(gè)JSContext,同時(shí)會(huì)創(chuàng)建一個(gè)新的JSVirtualMachine */(instancetype)init;/* 在指定虛擬機(jī)上創(chuàng)建一個(gè)JSContext */(instancetype)initWithVirtualMachine:(JSVirtualMachine*)virtualMachine;/* 執(zhí)行一段JS代碼,返回最后生成的一個(gè)值 */(JSValue*)evaluateScript:(NSString*)script;/* 執(zhí)行一段JS代碼,并將sourceURL認(rèn)作其源碼URL(僅作標(biāo)記用) */-(JSValue*)evaluateScript:(NSString*)script withSourceURL:(NSURL*)sourceURLNS_AVAILABLE(10_10,8_0);/* 獲取當(dāng)前執(zhí)行的JavaScript代碼的context */+(JSContext*)currentContext;/* 獲取當(dāng)前執(zhí)行的JavaScript function*/+(JSValue*)currentCalleeNS_AVAILABLE(10_10,8_0);/* 獲取當(dāng)前執(zhí)行的JavaScript代碼的this */+(JSValue*)currentThis;/* Returns the arguments to the current native callback from JavaScript code.*/+(NSArray*)currentArguments;/* 獲取當(dāng)前context的全局對(duì)象。WebKit中的context返回的便是WindowProxy對(duì)象*/@property(readonly,strong)JSValue*globalObject;@property(strong)JSValue*exception;@property(copy)void(^exceptionHandler)(JSContext*context,JSValue*exception);@property(readonly,strong)JSVirtualMachine*virtualMachine;@property(copy)NSString*nameNS_AVAILABLE(10_10,8_0);@end

2. JSContext訪問(wèn)JS對(duì)象

一個(gè)JSContext對(duì)象對(duì)應(yīng)了一個(gè)全局對(duì)象(global object)。例如web瀏覽器中中的JSContext,其全局對(duì)象就是window對(duì)象。在其他環(huán)境中,全局對(duì)象也承擔(dān)了類似的角色,用來(lái)區(qū)分不同的JavaScript context的作用域。全局變量是全局對(duì)象的屬性,可以通過(guò)JSValue對(duì)象或者context下標(biāo)的方式來(lái)訪問(wèn)。

一言不合上代碼:

JSValue*value=[context evaluateScript:@"var a = 1+2*3;"];NSLog(@"a = %@",[context objectForKeyedSubscript:@"a"]);NSLog(@"a = %@",[context.globalObject objectForKeyedSubscript:@"a"]);NSLog(@"a = %@",context[@"a"]);/Output:a=7a=7a=7

這里列出了三種訪問(wèn)JavaScript對(duì)象的方法

通過(guò)context的實(shí)例方法objectForKeyedSubscript

通過(guò)context.globalObject的objectForKeyedSubscript實(shí)例方法

通過(guò)下標(biāo)方式

設(shè)置屬性也是對(duì)應(yīng)的。

API Reference

/* 為JSContext提供下標(biāo)訪問(wèn)元素的方式 */@interfaceJSContext(SubscriptSupport)/* 首先將key轉(zhuǎn)為JSValue對(duì)象,然后使用這個(gè)值在JavaScript context的全局對(duì)象中查找這個(gè)名字的屬性并返回 */(JSValue*)objectForKeyedSubscript:(id)key;/* 首先將key轉(zhuǎn)為JSValue對(duì)象,然后用這個(gè)值在JavaScript context的全局對(duì)象中設(shè)置這個(gè)屬性。

可使用這個(gè)方法將native中的對(duì)象或者方法橋接給JavaScript調(diào)用 */(void)setObject:(id)object forKeyedSubscript:(NSObject*)key;@end/* 例如:以下代碼在JavaScript中創(chuàng)建了一個(gè)實(shí)現(xiàn)是Objective-C block的function */context[@"makeNSColor"]=^(NSDictionary*rgb){float r=[rgb[@"red"]floatValue];float g=[rgb[@"green"]floatValue];float b=[rgb[@"blue"]floatValue];return[NSColor colorWithRed:(r/255.f)green:(g/255.f)blue:(b/255.f)alpha:1.0];};JSValue*value=[context evaluateScript:@"makeNSColor({red:12, green:23, blue:67})"];

五、 JSValue

一個(gè)JSValue實(shí)例就是一個(gè)JavaScript值的引用。使用JSValue類在JavaScript和native代碼之間轉(zhuǎn)換一些基本類型的數(shù)據(jù)(比如數(shù)值和字符串)。你也可以使用這個(gè)類去創(chuàng)建包裝了自定義類的native對(duì)象的JavaScript對(duì)象,或者創(chuàng)建由native方法或者block實(shí)現(xiàn)的JavaScript函數(shù)。

每個(gè)JSValue實(shí)例都來(lái)源于一個(gè)代表JavaScript執(zhí)行環(huán)境的JSContext對(duì)象,這個(gè)執(zhí)行環(huán)境就包含了這個(gè)JSValue對(duì)應(yīng)的值。每個(gè)JSValue對(duì)象都持有其JSContext對(duì)象的強(qiáng)引用,只要有任何一個(gè)與特定JSContext關(guān)聯(lián)的JSValue被持有(retain),這個(gè)JSContext就會(huì)一直存活。通過(guò)調(diào)用JSValue的實(shí)例方法返回的其他的JSValue對(duì)象都屬于與最始的JSValue相同的JSContext。

每個(gè)JSValue都通過(guò)其JSContext間接關(guān)聯(lián)了一個(gè)特定的代表執(zhí)行資源基礎(chǔ)的JSVirtualMachine對(duì)象。你只能將一個(gè)JSValue對(duì)象傳給由相同虛擬機(jī)管理(host)的JSValue或者JSContext的實(shí)例方法。如果嘗試把一個(gè)虛擬機(jī)的JSValue傳給另一個(gè)虛擬機(jī),將會(huì)觸發(fā)一個(gè)Objective-C異常。

1. JSValue類型轉(zhuǎn)換

JSValue提供了一系列的方法將native與JavaScript的數(shù)據(jù)類型進(jìn)行相互轉(zhuǎn)換:

2. NSDictionary與JS對(duì)象

NSDictionary對(duì)象以及其包含的keys與JavaScript中的對(duì)應(yīng)名稱的屬性相互轉(zhuǎn)換。key所對(duì)應(yīng)的值也會(huì)遞歸地進(jìn)行拷貝和轉(zhuǎn)換。

[context evaluateScript:@"var color = {red:230, green:90, blue:100}"];//js->native 給你看我的顏色JSValue*colorValue=context[@"color"];NSLog(@"r=%@, g=%@, b=%@",colorValue[@"red"],colorValue[@"green"],colorValue[@"blue"]);NSDictionary*colorDic=[colorValue toDictionary];NSLog(@"r=%@, g=%@, b=%@",colorDic[@"red"],colorDic[@"green"],colorDic[@"blue"]);//native->js 給你點(diǎn)顏色看看context[@"color"]=@{@"red":@(0),@"green":@(0),@"blue":@(0)};[context evaluateScript:@"log('r:'+color.red+'g:'+color.green+' b:'+color.blue)"];Output:r=230,g=90,b=100r=230,g=90,b=100r:0g:0b:0

可見(jiàn),JS中的對(duì)象可以直接轉(zhuǎn)換成Objective-C中的NSDictionary,NSDictionary傳入JavaScript也可以直接當(dāng)作對(duì)象被使用。

3. NSArray與JS數(shù)組

NSArray對(duì)象與JavaScript中的array相互轉(zhuǎn)轉(zhuǎn)。其子元素也會(huì)遞歸地進(jìn)行拷貝和轉(zhuǎn)換。

[context evaluateScript:@“varfriends=['Alice','Jenny','XiaoMing']"];//js->native 你說(shuō)哪個(gè)是真愛(ài)?JSValue*friendsValue=context[@"friends"];NSLog(@"%@, %@, %@",friendsValue[0],friendsValue[1],friendsValue[2]);NSArray*friendsArray=[friendsValue toArray];NSLog(@"%@, %@, %@",friendsArray[0],friendsArray[1],friendsArray[2]);//native->js 我覺(jué)XiaoMing和不不錯(cuò),給你再推薦個(gè)Jimmycontext[@"girlFriends"]=@[friendsArray[2],@"Jimmy"];[context evaluateScript:@"log('girlFriends :'+girlFriends[0]+' '+girlFriends[1])"];

Output:

Alice,Jenny,XiaoMingAlice,Jenny,XiaoMinggirlFriends:XiaoMing Jimmy

4. Block/函數(shù)和JS function

Objective-C中的block轉(zhuǎn)換成JavaScript中的function對(duì)象。參數(shù)以及返回類型使用相同的規(guī)則轉(zhuǎn)換。

將一個(gè)代表native的block或者方法的JavaScript function進(jìn)行轉(zhuǎn)換將會(huì)得到那個(gè)block或方法。

其他的JavaScript函數(shù)將會(huì)被轉(zhuǎn)換為一個(gè)空的dictionary。因?yàn)镴avaScript函數(shù)也是一個(gè)對(duì)象。

5. OC對(duì)象和JS對(duì)象

對(duì)于所有其他native的對(duì)象類型,JavaScriptCore都會(huì)創(chuàng)建一個(gè)擁有constructor原型鏈的wrapper對(duì)象,用來(lái)反映native類型的繼承關(guān)系。默認(rèn)情況下,native對(duì)象的屬性和方法并不會(huì)導(dǎo)出給其對(duì)應(yīng)的JavaScript wrapper對(duì)象。通過(guò)JSExport協(xié)議可選擇性地導(dǎo)出屬性和方法。

后面會(huì)詳細(xì)講解對(duì)象類型的轉(zhuǎn)換。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容