http://blog.ilibrary.me/2016/12/25/react-native-internaljavascript
------------------------------------------------------------------------------------css
術語、簡寫約定html
名詞/縮寫 | 解釋 |
---|---|
React Native | Facebook出的跨平臺應用構建框架 |
ReactJS | Facebook出的Web UI JS框架,具備革新性的編程模式 |
React | 若無特殊說明,React就是ReactJS |
NodeJS | 基於JavaScript V8 Engine 的一個javascript runtime |
RN | React Native的縮寫 |
N2J | Native to JavaScript call |
J2N | JavaScript to Native call |
JavaScript | 咱們常說的JavaScript腳本語言 |
JS | JavaScript |
JS Engine | JavaScript腳本解釋執行引擎 |
JavaScriptCore | iOS/Android 平臺上默認的JS Engine, 來源於Webkit, 無特殊說明狀況下面,本文全部解釋都會默認基於JavaScriptCore |
JSC | JavaScriptCore |
JSX | JavaScript的一個語言擴展,在JSX裏面你能夠用標記語言來描述React組件。不用JSX也能夠寫ReactJS,可是有JSX會高效不少 |
Bridge | React Native裏面實現JS和原生代碼互相調用的模塊,該詞指向兩個概念,一個是RCTBridge,找個用來管理Bridge的組件,第二個是全部自定義的用於RN的原生功能。全部原生功能想要在JS裏面使用都須要有定製的bridge來支持,因此咱們把這個定製的模塊(包括native和js端)也稱之爲bridge |
React Native是Facebook出品的一個革新性的跨平臺UI框架,跨平臺不是它最大的亮點,它背後的[React]才應該是它的神奇說在,也是它革新所在。我在另一篇博客React Native系列(3) - props和state中有詳細的分析。若是非要用一兩句來總結它的偉大,那就是給把web開發中的無狀態開發模式經過React實現了。 那些數不清的狀態組合纔是桌面應用和手機應用複雜的源頭。java
本文目的是經過源碼分析,詳細解釋React Native框架的內部結構及運行原理。文章會比較長,組織上會盡量由淺入深來說。適合的讀者對象是對React Native開發有必定基礎的開發者。node
分析的代碼是基於0.50.3, 基於iOS平臺的實現,包括原生的代碼,js框架和打包器。從代碼打包,react native初始化,js加載到運行以及錯誤處理。安卓平臺的Java代碼解讀不在本文覆蓋範圍。react
ReactJS是一個很是具備革新性的web UI框架,很是簡單易用。它的virtual dom和data driven編程模式對現有的UI編程模式是一種顛覆, 極大簡化了UI應用複雜狀態的管理,很是值得你們去試一下。ReactJS配上Redux, 你會發現作複雜多狀態的應用竟然能夠如此簡單! React Native強大的緣由就在於它是基於ReactJS的。相信讀這篇文章的人大部分以對ReactJS有必定的瞭解,這裏就很少說。android
講React Native以前,瞭解JavaScriptCore會有幫助,也是必要的。React Native的核心驅動力就來自於JS Engine. 你寫的全部JS和JSX代碼都會被JS Engine來執行, 沒有JS Engine的參與,你是沒法享受ReactJS給原生應用開發帶來的便利的。在iOS上,默認的就是JavaScriptCore, iOS 7以後的設備都支持. iOS 不容許用本身的JS Engine. JavaScriptCore來自於WebKit, 因此,安卓上默認也是用JavaScriptCore.ios
因此,你深刻了解React Native的第一站應該是 JavaScriptCore.git
nshipster.cn有一篇文章 對JavaScriptCore的使用有一些簡單的使用說明。讀完這篇文章你會發現,JavaScriptCore使用起來是很是簡單的,這得益於JavaScript的簡潔設計。因此,本文不會花精力講解JavaScriptCore。github
相信有JavaScript和OC基礎的同窗只須要花大概30秒就能夠把上面的這篇文章讀完。
恭喜你! 當你讀完上面的那篇文章之後,你已經掌握了本身創造React Native框架的核心技術!一點也不誇張,JavaScriptCore在iOS平臺上給React Native提供的接口也僅限於那幾個接口,你弄明白了JavaScriptCore那幾個接口, React Native 剩下的魔法祕密均可以順藤摸瓜來分析了。
本文接下來要講解的就是Facebook圍繞這幾個接口以及用一個React來顛覆整個native開發所作的精妙設計和封裝。你若是想本身作一個基於JS Engine作一個相似React Native的框架出來,建議研究JSPatch, 另外,還有一個外國朋友寫了一個博客講解用Edge JS引擎本身動手寫一個bridge. React Native的封裝很是龐大,涉及了不少的話題,直接與JS Engine相關的很少。
瀏覽器經過Dom Render來渲染全部的元素.
瀏覽器有一整套的UI控件,樣式和功能都是按照html標準實現的。
瀏覽器能讀懂html和css。
html告訴瀏覽器繪製什麼控件(html tag),css告訴瀏覽器每一個類型的控件(html tag)具體長什麼樣。
瀏覽器的主要做用就是經過解析html來造成dom樹,而後經過css來點綴和裝飾樹上的每個節點。
UI的描述和呈現分離開了。
在react native 裏面,1和2是不變的,也是用html語言描述頁面有哪些功能,而後stylesheet告訴瀏覽器引擎每一個控件應該長什麼樣。而且和瀏覽器用的是同一個引擎。
在步驟3裏面UI控件再也不是瀏覽器內置的控件,而是react native本身實現的一套UI控件(兩套,android一套,ios一套),這個切換是在MessageQueque中進行的,而且還能夠發現,他們tag也是不同的。
Javascript在react native裏面很是重要,
先上一副React Native 架構圖,這是我在內部培訓的時候畫的一副圖。
React Native最重要的三個概念應該就是React Native、React和JavascriptCore.
理解這三者的關係之後你們就能夠本身去深刻研究React Native了。
React是一個純JS庫,全部的React代碼和全部其它的js代碼都須要JS Engine來解釋執行。由於種種緣由,瀏覽器裏面的JS代碼是不容許調用自定義的原生代碼的,而React又是爲瀏覽器JS開發的一套庫,因此,比較容易理解的事實是React是一個純JS庫,它封裝了一套Virtual Dom的概念,實現了數據驅動編程的模式,爲複雜的Web UI實現了一種無狀態管理的機制, 標準的HTML/CSS以外的事情,它無能爲力。調用原生控件,驅動聲卡顯卡,讀寫磁盤文件,自定義網絡庫等等,這是JS/React無能爲力的。
你能夠簡單理解爲React是一個純JS 函數, 它接受特定格式的字符串數據,輸出計算好的字符串數據。
JS Engine負責調用並解析運行這個函數。
React Native呢? 它比較複雜。複雜在哪裏?前面咱們說了React 是純JS庫,意味着React只能運行JS代碼,經過JS Engine提供的接口(Html Tag)繪製html支持的那些元素,驅動有限的聲卡顯卡。簡單點說, React只能作瀏覽器容許它作的事情, 不能調用原生接口, 不少的事情也只能乾瞪眼。
React Native它可不同。
第一點,驅動關係不同。前面咱們說的是, JS Engine來解析執行React腳本, 因此,React由瀏覽器(最終仍是JS Engine)來驅動. 到了React Native這裏,RN的原生代碼(Timer和用戶事件)驅動JS Engine, 而後JS Engine解析執行React或者相關的JS代碼,而後把計算好的結果返回給Native code. 而後, Native code 根據JS計算出來的結果驅動設備上全部能驅動的硬件。重點,全部的硬件。也就是說,在RN這裏,JS代碼已經擺脫JS Engine(瀏覽器)的限制,能夠調用全部原生接口啦!
第二點, 它利用React的Virtual Dom和數據驅動編程概念,簡化了咱們原生應用的開發, 同時,它不禁瀏覽器去繪製,只計算出繪製指令,最終的繪製仍是由原生控件去負責,保證了原生的用戶體驗。
React組件結構(圖文)
React Native組件結構(圖文)
驅動硬件的能力決定能一個軟件能作多大的事情,有多大的主控性。研究過操做系統底層東西或者彙編的同窗明白,咱們大部分時候寫的代碼是受限的代碼,不少特權指令咱們是無法使用的,不少設備咱們是不容許直接驅動的。咱們如今的編程裏面幾乎已經沒有人提中斷了,沒有中斷,硬件的操做幾乎會成爲一場災難.
在必定程度上,React Native和NodeJS有殊途同歸之妙。它們都是經過擴展JavaScript Engine, 使它具有強大的本地資源和原生接口調用能力,而後結合JavaScript豐富的庫和社區和及其穩定的跨平臺能力,把javascript的魔力在瀏覽器以外的地方充分發揮出來。
JavaScriptCore + ReactJS + Bridges 就成了React Native。
#深刻 Bridge 前面有提到, RN厲害在於它能打通JS和Native Code, 讓JS可以調用豐富的原生接口,充分發揮硬件的能力, 實現很是複雜的效果,同時能保證效率和跨平臺性。
打通RN任督二脈的關鍵組件就是Bridge. 在RN中若是沒有Bridge, JS仍是那個JS,只能調用JS Engine提供的有限接口,繪製標準html提供的那些效果,那些攝像頭,指紋,3D加速,聲卡, 視頻播放定製等等,JS都只能流流口水,原生的、平臺相關的、設備相關的效果作不了, 除非對瀏覽器進行定製。
Bridge的做用就是給RN內嵌的JS Engine提供原生接口的擴展供JS調用。全部的本地存儲、圖片資源訪問、圖形圖像繪製、3D加速、網絡訪問、震動效果、NFC、原生控件繪製、地圖、定位、通知等都是經過Bridge封裝成JS接口之後注入JS Engine供JS調用。理論上,任何原生代碼能實現的效果均可以經過Bridge封裝成JS能夠調用的組件和方法, 以JS模塊的形式提供給RN使用。
每個支持RN的原生功能必須同時有一個原生模塊和一個JS模塊,JS模塊是原生模塊的封裝,方便Javascript調用其接口。Bridge會負責管理原生模塊和對應JS模塊之間的溝通, 經過Bridge, JS代碼可以驅動全部原生接口,實現各類原生酷炫的效果。
RN中JS和Native分隔很是清晰,JS不會直接引用Native層的對象實例,Native也不會直接引用JS層的對象實例(全部Native和JS互掉都是經過Bridge層會幾個最基礎的方法銜接的)。
Bridge 原生代碼負責管理原生模塊並生成對應的JS模塊信息供JS代碼調用。每一個功能JS層的封裝主要是針對ReactJS作適配,讓原生模塊的功能可以更加容易被用ReactJS調用。MessageQueue.js是Bridge在JS層的代理,全部JS2N和N2JS的調用都會通過MessageQueue.js來轉發。JS和Native之間不存在任何指針傳遞,全部參數都是字符串傳遞。全部的instance都會被在JS和Native兩邊分別編號,而後作一個映射,而後那個數字/字符串編號會作爲一個查找依據來定位跨界對象。
本節介紹如下模塊
RCTRootView是React Native加載的地方,是萬物之源。從這裏開始,咱們有了JS Engine, JS代碼被加載進來,對應的原生模塊也被加載進來,而後js loop開始運行。 js loop的驅動來源是Timer和Event Loop(用戶事件). js loop跑起來之後應用就能夠持續不停地跑下去了。
若是你要經過調試來理解RN底層原理,你也應該是從RCTRootView着手,順藤摸瓜。
每一個項目的AppDelegate.m
的- (BOOL)application:didFinishLaunchingWithOptions:
裏面均可以看到RCTRootView的初始化代碼,RCTRootView初始化完成之後,整個React Native運行環境就已經初始化好了,JS代碼也加載完畢,全部React的繪製都會有這個RCTRootView來管理。
RCTRootView作的事情以下:
AppRegistry.runApplication
正式啓動RN JS代碼,從Root Component(
一個App能夠有多個RCTRootView, 初始化的時候須要手動傳輸Bridge
作爲參數,全局能夠有多個RCTRootView, 可是隻能有一個Bridge
.
若是你作過React Native和原生代碼混編,你會發現混編就是把AppDelegate
裏面那段初始化RCTRootView
的代碼移動到須要混編的地方,而後把RCTRootView
作爲一個普通的subview來加載到原生的view裏面去,很是簡單。不過這地方也要注意處理好單Bridge
實例的問題,同時,混編裏面要注意RCTRootView
若是銷燬過早可能會引起JS回調奔潰的問題。
RCTRootContentView reactTag在默認狀況下爲1. 在Xcode view Hierarchy debugger 下能夠看到,最頂層爲RCTRootView, 裏面嵌套的是RCTRootContentView
, 從RCTRootContentView開始,每一個View都有一個reactTag.
RCTRootView繼承自UIView, RCTRootView主要負責初始化JS Environment和React代碼,而後管理整個運行環境的生命週期。 RCTRootContentView繼承自RCTView, RCTView繼承自UIView, RCTView封裝了React Component Node更新和渲染的邏輯, RCTRootContentView會管理全部react ui components. RCTRootContentView同時負責處理全部touch事件.
這是一個加載和初始化專用類,用於前期JS的初始化和原生代碼的加載。
RCTBridgeModule
protocol的類, 供JS後期使用.若是RCTBridge是總裁, 那麼RCTBatchedBridge就是副總裁。前者負責發號施令,後者負責實施落地。
這是實現遠程代碼加載的核心。熱更新,開發環境代碼加載,靜態jsbundle加載都離不開這個工具。
封裝了基礎的JS和原生代碼互掉和管理邏輯,是JS引擎切換的基礎。經過不一樣的RCTCOntextExecutor來適配不一樣的JS Engine,讓咱們的React JS能夠在iOS、Android、chrome甚至是自定義的js engine裏面執行。這也是爲什麼咱們能在chrome裏面直接調試js代碼的緣由。
加載和管理全部和JS有交互的原生代碼。把須要和JS交互的代碼按照必定的規則自動封裝成JS模塊。
記錄全部原生代碼的導出函數地址(JS裏面是不能直接持有原生對象的),同時生成對應的字符串映射到該函數地址。JS調用原生函數的時候會經過message的形式調用過來。
MessageQueue
會幫忙把Method翻譯成MethodID, 而後轉發消息給原生代碼,傳遞函數簽名和參數給原生MessageQueue
, 最終給RCTModuleMethod解析調用最終的方法MessageQueue
會把回調對象轉化成一個一次性的block id, 而後傳遞給RCTModuleMethod, 最終由RCTModuleMethod解析調用。基本上和方法調用同樣,只不過生命週期會不同,block是動態生成的,要及時銷燬,要否則會致使內存泄漏。注:
其實是不存在原生MessageQueue對象模塊的,JS的MessageQueue對應到原生層就是RCTModuleData & RCTModuleMethod的組合, MessageQueue的到原生層的調用先通過RCTModuleData和RCTModuleMethod翻譯成原生代碼調用,而後執行.
這是核心中的核心。整個react native對瀏覽器內核是未作任何定製的,徹底依賴瀏覽器內核的標準接口在運做。它怎麼實現UI的徹底定製的呢?它實際上未使用瀏覽器內核的任何UI繪製功能,注意是未使用UI繪製功能。它利用javascript引擎強大的DOM操做管理能力來管理全部UI節點,每次刷新前把全部節點信息更新完畢之後再給yoga作排版,而後再調用原生組件來繪製。javascript是整個系統的核心語言。
咱們能夠把瀏覽器當作一個盒子,javascript引擎是盒子裏面的總管,DOM是javascript引擎內置的,javascript和javascript引擎也是無縫連接的。react native是怎麼跳出這個盒子去調用外部原生組件來繪製UI的呢?祕密就在MessageQueue。
javascript引擎對原生代碼的調用都是經過一套固定的接口來實現,這套接口的主要做用就是記錄原生接口的地址和對應的javascript的函數名稱,而後在javascript調用該函數的時候把調用轉發給原生接口
React Native的初始化從RootView開始,默認在AppDelegate.m:- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
裏面會有RootViewd的初始化邏輯,調試的時候能夠從這裏入手。
React Native的初始化分爲幾個步驟:
這裏討論的主要是RN相關的原生代碼和用戶自定義的RN模塊的原生代碼的加載和初始化。原生代碼初始化主要分兩步:
RCTModule
的原生模塊,生成一個json格式的模塊信息,裏面包含模塊名稱和方法名稱,而後注入到JS Engine, 由MessageQueue記錄下來。原生代碼在生成json模塊信息的時候同時會在原生代碼這邊維護一個名稱字典,用來把模塊和方法的名稱映射到原生代碼的地址上去,用於JS調用原生代碼的翻譯。接下來咱們就一步一步詳細講解原生代碼的初始化。
RN的初始化是從RCRootView開始的,全部的繪製都會在這個RootView裏面進行(Alert除外).
RootView作的第一件事情就是初始化一個空的JS Engine。 這個空的JS Engine裏面包含一些最基礎的模塊和方法(fetch, require, alert等), 沒有UI繪製模塊。 RN的工做就是替換這些基礎的模塊和方法,而後把RN的UI繪製模塊加載並注入到JS Engine.
JS Engine不直接管理UI的繪製。
在OC裏面,全部NativeModules要加載進JS Engine都必須遵循必定的協議(protocol)。
模塊(OC裏面的類)須要聲明爲<RCTBridgeModule>
, 而後在類裏面還必須調用宏RCT_EXPORT_MODULE()
用來定義一個接口告訴JS當前模塊叫什麼名字。這個宏能夠接受一個可選的參數,指定模塊名,不指定的狀況下就取類名。
對應的JS模塊在初始化的時候會調用原生類的[xxx new]
方法.
模塊聲明爲<RCTBridgeModule>
後只是告訴Native Modules這有一個原生模塊,是一個空的模塊。要導出任何方法給JS使用都必須手動用宏RCT_EXPORT_METHOD
來導出方法給JS用.
全部的原生模塊都會註冊到NativeModules這一個JS模塊下面去,你若是想要讓本身的模塊成爲一個頂級模塊就必須再寫一個JS文件封裝一遍NativeModules裏面的方法。
你若是想本身的方法導出就默認成爲頂級方法,那麼你須要一個手動去調用JSC的接口,這個在前面章節有講解。 不建議這樣作,由於這樣你會失去跨JS引擎的便利性。
你能夠導出常量到JS裏面去, 模塊初始化的時候會堅持用戶是否有實現constantsToExport
方法, 接受一個常量詞典。
- (NSDictionary *)constantsToExport { return @{ @"firstDayOfTheWeek": @"Monday" };// JS裏面能夠直接調用 ModuleName.firstDayOfTheWeek獲取這個常量 }
常量只會在初始化的時候調用一次,動態修改該方法的返回值無效
全部標記爲RCT_EXPORT_MODULE
的模塊都會在程序啓動的時候自動註冊好這些模塊,主要是記錄模塊名和方法名。只是註冊,不必定會初始化。
Native Modules導出宏具體使用方法見官方文檔Native Modules
React Native的NativeModules是有延遲加載機制的。App初始化的時候
React Native有三個重要的線程:
能夠看到Shadow queue是queue
而不是thread
, 在iOS裏面queue
是thread
之上的一層抽象,GCD裏面的一個概念,建立queue
的時候能夠指定是並行的仍是串行的。也就是說,一個queue
可能對應多個thread
。
As mentioned above, every module will have it’s own GCD Queue by default, unless it specifies the queue it wants to run on, by implementing the -methodQueue method or synthesizing the methodQueue property with a valid queue.
待更新
待更新
Call Cyle
Executor Environment, 原文
Js to Java call, 出處bugly
消息循環, 出處