scratch3.0 虛擬機(jī)源碼分析

scratch軟件的邏輯不復(fù)雜,就是用blockly生成語句塊,然后用虛擬機(jī)抽象成底層語法,最后再調(diào)用render渲染到界面,但是因?yàn)榫W(wǎng)上幾乎沒有資料,源代碼又嵌套的極深,看起來還是很頭疼的,所以我把我這一周看代碼的心得分享一些出來,以后再慢慢更新.希望大家也能少走些彎路.

首先什么是虛擬機(jī) : 用來屏蔽底層硬件差異和dom渲染差異 , 使得程序可以跨端移植 , react本質(zhì)上也是虛擬機(jī),虛擬dom屏蔽設(shè)備渲染差異( dom只有pc瀏覽器能識(shí)別 , 但虛擬dom是js對(duì)象 , 因而在手機(jī)上能解析成viewPort),native屏蔽底層硬件差異 ,使得程序可以在Android和ios都可以運(yùn)行 . scratch-vm作用:使用虛擬io屏蔽底層差異,使用render屏蔽ui差異,使軟件可以跨端

命名規(guī)則:靜止?fàn)顟B(tài)的標(biāo)簽叫做target(包含stage和sprite),運(yùn)行狀態(tài)的標(biāo)簽叫做thread.舞臺(tái)叫playgroud,渲染/停止渲染叫做grow/ungrow,監(jiān)視器叫monitor(每個(gè)target可以有一個(gè)),backpack是背包,workspace是舞臺(tái)上block的合集,runtime是內(nèi)核.editingTarget是正在編輯的target,custom和backdrop都是target的皮膚,僅適用的對(duì)象不同,sprite的皮膚叫做custom,stage的皮膚叫做backdrop , custom可以有多個(gè) ,因?yàn)槎鄠€(gè)皮膚幀可以產(chǎn)生動(dòng)畫

monitor 類似于看門狗,由于動(dòng)畫是連續(xù)快速渲染的,有時(shí)會(huì)渲出undefined等情況,這樣會(huì)造成丟幀現(xiàn)象,所以我們把每一幀圖像在update時(shí)強(qiáng)制用monitor進(jìn)行檢查,如果丟幀就回滾回上一幀,這樣就感受不到丟幀了, monitor可以手動(dòng)增刪更新和隱藏顯示,在vm/engine/runtime 在2076行左右

scratch運(yùn)行時(shí)虛擬機(jī)分為標(biāo)準(zhǔn)模式(standard,按照默認(rèn)機(jī)器頻率刷新),兼容模式(compatibility,渲染速度是30tps,人眼會(huì)感受到畫面頻閃,但動(dòng)作流暢性要好一些,可以兼容2.0的項(xiàng)目,2.0是as語言編寫的,我猜測應(yīng)該是使用的n制渲染來兼容差值掃描渲染,由于n制不是串行掃描,如果使用3.0模式的渲染可能會(huì)飆到120fps,所以他搞了這樣一種兼容方案)和嵌入模式(加速模式,重繪次數(shù)會(huì)變少).

runtime:用于存儲(chǔ)block,sprite和虛擬IO,內(nèi)置一個(gè)sequencer隊(duì)列(這是一個(gè)全局任務(wù)隊(duì)列,每次對(duì)target操作都會(huì)入隊(duì),在js時(shí)鐘tick時(shí)執(zhí)行,直至為空為止),一個(gè)targets隊(duì)列(每個(gè)tagers[i]生命周期與對(duì)應(yīng)的target關(guān)聯(lián),target是局部的,可以被銷毀,在需要時(shí)會(huì)重新創(chuàng)建,類似于路由模式,而sequencer是全局的,所有對(duì)target的操作都要在這里排隊(duì))

標(biāo)簽切換 在vm/engine/runtime 900行左右,主要控制在scratch中各個(gè)sprite的標(biāo)簽欄的切換,排序和銷毀,標(biāo)簽不能被執(zhí)行,除非接收到廣播消息或者有用戶交互時(shí)才會(huì)自執(zhí)行.標(biāo)簽的管理在targets隊(duì)列當(dāng)中,標(biāo)簽之間的通信參見第七條

target并不是從runtime中初始化的,而是在vm外殼文件(virtual-machine)中通過io函數(shù)調(diào)用的,區(qū)分sb2,sb3的文件結(jié)構(gòu)載入,downloadProjectId是從網(wǎng)絡(luò)下載,loadProjectLocal是從本地加載,fromJSOM可以加載2.0版本(3.0有特殊的meta字段,2.0是as格式的腳本文件,如果載入之后發(fā)現(xiàn)是2.0,會(huì)zip壓縮之后再blob二進(jìn)制化,這樣3.0版本就能識(shí)別了),項(xiàng)目工程載入之后才能installtarget,target類位于vm/engine/target,通過調(diào)用target中的函數(shù)就可以控制注冊(cè)的block了,例如在lib/empty-assets中有個(gè)空的項(xiàng)目文件,到當(dāng)項(xiàng)目加載時(shí),調(diào)用了vm外殼中的的installTargets函數(shù),會(huì)將target中的custom,objname,scripts等屬性加載進(jìn)來.

線程間的通信(重要) runtime管理著sprite,runtime與sprite之間用廣播來通信,但當(dāng)sprite之間需要通信時(shí),這變的很復(fù)雜,所以runtime在線程調(diào)度層面實(shí)現(xiàn)了一個(gè)迷你redux,在dispatch文件夾中.central-dispatch是一個(gè)單例模式,會(huì)在vm外殼runtime初始化的時(shí)候調(diào)用他一次(virtual-machine第55行,壞點(diǎn)監(jiān)控寫的非常秀,值得借鑒)把vm設(shè)置成中央總線,他有一個(gè)services對(duì)象來注冊(cè)全局路由,之后在文件中引入dispatch就可以在sprite之間互傳參數(shù)了,在/extension-support/extension-manager中,初始化extension時(shí)調(diào)用dispatch.addWork(),會(huì)為extension新建一個(gè)工作線程,加入到線程池,由于在vm中installTargets時(shí)會(huì)生成一個(gè)extensionPromises隊(duì)列,先異步加載ext再加載target,這樣就保證了每個(gè)target至少會(huì)有一個(gè)worker,這樣就實(shí)現(xiàn)了多線程計(jì)算,target中會(huì)被注入一個(gè)dispatch變量,使用dispatch就可以對(duì)總線狀態(tài)進(jìn)行推送(例如dispatch.call(serviceName)),進(jìn)而實(shí)現(xiàn)了線程調(diào)度.

block渲染(重要) 當(dāng)vm啟動(dòng)時(shí),在runtime入口定義了defaultBlockPackages類,這里面聲明了每個(gè)block塊的功能函數(shù)(比如repeatUntil,moveTo等),在vm/engine/runtime 715行,有一個(gè)_registerBlockPackages函數(shù),會(huì)加載所有block塊動(dòng)作,然后通過聲明基類函數(shù)getPrimitives取得各個(gè)模塊中的block預(yù)定義動(dòng)作,之后通過訂閱分發(fā)(路由模式)的方式生成packagePrimitives類,這樣block塊的特定功能就都可以在vm中使用了(此時(shí)已block已按category分類好),但是這種方式無法處理包含交互的block(如control和event,因?yàn)樗麄儾粌H要響應(yīng)用戶的操作,又要監(jiān)聽其他事件,如greenFlag被點(diǎn)擊等),所以他們?cè)诨愔杏謹(jǐn)U展了hat類,其實(shí)就是一個(gè)二級(jí)路由,原理同上,類似的二級(jí)路由還有monitor類,具體功能參見第二條

關(guān)于hat,這一般是一個(gè)target程序的起點(diǎn)(除非有event的全局廣播事件),vm啟動(dòng)時(shí)會(huì)在runtime維護(hù)一個(gè)hat隊(duì)列,類似于microTick,vm內(nèi)核中有一大堆類step(stepTreads,stepTread等等)函數(shù)會(huì)調(diào)用sequencer,sequencer中有個(gè)_step函數(shù),_step會(huì)將所有隊(duì)列中的函數(shù)執(zhí)行完然后銷毀自己,然后在新的tick如果產(chǎn)生新的任務(wù)則重啟隊(duì)列,所以hat隊(duì)列是全局共享的.

關(guān)于虛擬IO,虛擬IO分為mouse,keyborad,BLE,clock等(名詞解釋:ble是藍(lán)牙,bt是BT下載,clock是系統(tǒng)時(shí)鐘,cloud是云端,keyboard是鍵盤,mouse是鼠標(biāo),mousewheel是鼠標(biāo)滾輪滾動(dòng),video是視頻渲染),stage中的所有按鍵,點(diǎn)擊事件全部被注冊(cè)到了lib/vm-listener-hoc中,然后從該文件中轉(zhuǎn)發(fā)給對(duì)應(yīng)的虛擬IO處理,當(dāng)有點(diǎn)擊事件發(fā)生時(shí),vm-listener首先捕獲事件,然后把消息推送至虛擬機(jī),虛擬機(jī)會(huì)定位到對(duì)應(yīng)的sprite或者帶有hats的sprite,執(zhí)行注冊(cè)的函數(shù).

虛擬IO的作用:scratch中自定義了一套key,每個(gè)key對(duì)應(yīng)一個(gè)sprite動(dòng)作(如uparrow,spaceDown等,scratch只認(rèn)這套key,不認(rèn)其他字符),虛擬鍵盤IO建立起了Ascll碼與scratch-key之間的聯(lián)系,而虛擬鼠標(biāo)IO主要是捕獲鼠標(biāo)在stage上的位置(舞臺(tái)中心默認(rèn)為(0,0),是一個(gè)480360的區(qū)域,如果鼠標(biāo)不在此區(qū)域,在查詢鼠標(biāo)位置時(shí),會(huì)強(qiáng)制設(shè)置成邊緣坐標(biāo),如左上角(-240,-180),如果在區(qū)域內(nèi),會(huì)通過遍歷sprite的drawableID來找到target,然后調(diào)用target中對(duì)應(yīng)的執(zhí)行程序,所以在只在gui當(dāng)中改變舞臺(tái)位置,渲染區(qū)域并不會(huì)改變,因?yàn)槌?80360的區(qū)域都被強(qiáng)制替換了),而虛擬ble,bt都是基于websocket的rpc,都是異步調(diào)用,與ajax的用法差不多

關(guān)于渲染:由于動(dòng)作是線性并可預(yù)測結(jié)束位置(比如:step 5 ),所以渲染器在簡單粗暴的比較前后兩幀的區(qū)別,如果沒有找到區(qū)別,則把上一幀直接作為動(dòng)作結(jié)束并返回

targets = targets.filter(target => !!target); 這是對(duì)象去空操作

return !target.hasOwnProperty('isOriginal') || target.isOriginal; 這是剔除clone的操作,因?yàn)閠arget.isOriginal為false時(shí),代表這是clone的對(duì)象,由于isOriginal屬性可能有也可能沒有,直接判斷target.isOriginal有可能會(huì)報(bào)錯(cuò),所以要先加一個(gè)hasOwnProperty判斷,如果不存在這個(gè)屬性就不往后判斷了

Object.is()比較兩個(gè)對(duì)象是否嚴(yán)格相等,因?yàn)?==有時(shí)候會(huì)有比較嚴(yán)重問題:如 +0 === -0 //true , NaN === NaN // false

static get RUNTIME_STARTED (){ return XXX},這是靜態(tài)繼承,他是屬性不是函數(shù),近似看成this.RUNTIME_STARTED就可以了,可以像這樣全局調(diào)用vm.RUNTIME_STARTED

gui界面 在vm/engine/runtime 在500行左右通過靜態(tài)繼承定義,(更改舞臺(tái)渲染器大小是在這里) 在外部使用vm.XXX調(diào)用,實(shí)質(zhì)上是在調(diào)用函數(shù)的取值器get() (更改舞臺(tái)大小和位置是個(gè)巨大的工程,因?yàn)閟cratch在虛擬IO(video,mouse),lib,gui的css和渲染器(render)中都定義了具體的數(shù)值,其他地方我暫時(shí)還沒發(fā)現(xiàn),稍有修改都可能引起bug)

gui界面的命名規(guī)則(重要):頂層藍(lán)框(有sratch圖標(biāo),可選語言)區(qū)域叫做menu-bar,下方選擇代碼/造型/聲音區(qū)域叫做tab-list(點(diǎn)擊可切換tabPanel),然后是下方:

一,左邊的2/3區(qū)域:左側(cè)舞臺(tái)叫做gui-blocks(可以選擇語句塊并拖放出來),其中最左側(cè)選擇類型欄叫做(blocklyToolbox,包含上方的scratchCategoryMenu(內(nèi)含運(yùn)動(dòng),聲音,外觀等選項(xiàng))和下方的gui_extension(添加擴(kuò)展按鈕)),中側(cè)叫做blocklyFlyout(顧名思義,當(dāng)拖拽block進(jìn)入此區(qū)域會(huì)被刪除)最右層叫做blocklyWorkspace(能夠盛放拖拽出的block,他的右下角有一個(gè)blocklyZoom組件(用來控制代碼塊舞臺(tái)的縮放),blocklyWorkspace還定義了一堆瀏覽器組件,比如ScrollbarVertical(這是一個(gè)水平滾動(dòng)條),比如blocklyFlyoutBackground(這是一個(gè)描邊功能),不能理解他為什么不用系統(tǒng)自帶的,費(fèi)力寫這么一大片代碼,然后看起來比系統(tǒng)默認(rèn)的還要丑..)

二,右邊1/3區(qū)域:上側(cè)叫做stage,其中包含(stage-header和stage-canvas(渲染舞臺(tái))),下側(cè)叫做gui-target,它是左右結(jié)構(gòu),左側(cè)叫做sprite-selector(選擇角色)(內(nèi)含sprite-info(設(shè)置大小,方向,名稱什么的),sprite-selector-item(角色圖標(biāo))和sprite-selector-add-button(角色選擇按鈕)),右側(cè)叫做target-pane(內(nèi)含stage-selector-header(舞臺(tái)字樣),selector-costume-canvas(渲染舞臺(tái)背景),stage-selector-add-button(舞臺(tái)選擇按鈕))

與scratch通信:在lib/vm-listener-hoc中定義,主要是把vm當(dāng)中導(dǎo)出的各種狀態(tài)映射到reducer當(dāng)中.

block定義結(jié)構(gòu),樣式和行為,Toolbox定義類型,用于在workspace中分類顯示,Blockly是總的控制函數(shù).block分為block和shadow類型(shadow是占位符,不是塊,提供block的默認(rèn)值,當(dāng)有其他其他塊占據(jù)他的位置時(shí)會(huì)被覆蓋),statement block指的是沒有輸出的block(只能在變量中添加,不能在流程中添加)

Blockly.Extensions.registerMutator(name, mixinObj, opt_helperFn, opt_blockList) 這是注冊(cè)皮膚編輯器,Blockly.Extensions.register在scratch中被影射成了Blockly.ScratchBlocks.VerticalExtensions,而Blockly.ScratchBlocks追蹤到最后是一個(gè)goog.require('Blockly.ScratchBlocks'),所有的goog.require函數(shù)鏈接一般指向的是輸出文件夾的一個(gè).compress.js文件,如果沒有就是指向scratch-block/core文件夾,如果還沒有就指向一個(gè).py文件,可以直接編譯出來,具體在package.json中配置

vm是在containers/gui.jsx中啟動(dòng)的,scratch中components是純函數(shù)組件,而在containers文件夾中會(huì)把同名components與redux和vm連接,同時(shí)進(jìn)行國際化,組件節(jié)流,版本控制,虛擬IO監(jiān)聽等操作,邏輯非常清晰.所有ui狀態(tài)在reducer/gui.js中進(jìn)行組裝然后統(tǒng)一導(dǎo)出,但是要注意scratch根目錄下的index.js是個(gè)假的入口文件,reducer真正是在lib/app-state-hoc中的AppStateHOC類組裝的,這是一個(gè)中間件,在入口函數(shù)render-gui中GUI組件使用compose函數(shù)進(jìn)行柯里化(將f(a)(b)(c)(d)變成f(a,b,c)(d)叫做柯里化)封裝了AppStateHOC, HashParserHOC,TitledHOC三個(gè)中間件,而AppStateHOC通過判斷是否需要加載paint和gui來加載不同的store,因此<Provider>也在這個(gè)組件當(dāng)中,guiMiddleware是一個(gè)封裝了throttle的柯里化函數(shù),按照中間件模式調(diào)用,用于為組件節(jié)流(如果一秒內(nèi)點(diǎn)了很多次,只會(huì)執(zhí)行兩次),封裝之后返回了經(jīng)過國際化(多語言模塊)和節(jié)流處理的高級(jí)組件(節(jié)流的實(shí)現(xiàn):當(dāng)createStore擁有enhancer參數(shù)時(shí),會(huì)返回一個(gè)enhancer(createStore)(reducer,state)的高級(jí)組件,這樣使用enhancer就可以實(shí)現(xiàn)組件的功能模塊化,類似于鏈?zhǔn)秸{(diào)用(一個(gè)函數(shù)只完成一個(gè)功能))

一次完整的vm調(diào)用過程,加載時(shí)先全局加載虛擬機(jī),初始化虛擬IO(參見第10,11條),然后查看網(wǎng)絡(luò)請(qǐng)求中l(wèi)ocation對(duì)象的hash,如果不能識(shí)別,直接在本地新建工程,并為工程賦予唯一ID值,如果能識(shí)別,從網(wǎng)絡(luò)或從本地加載工程(參考第六條),將舞臺(tái)推入render生成渲染器,再把渲染器推入vm,然后調(diào)用Blockly.inject函數(shù)在一個(gè)dom(類似于div#id)上面,初始化workspace和flyoutWorkspace樣式(之所以有兩個(gè)workspace,是因?yàn)閣orkspace的宿主是target,當(dāng)sprite銷毀時(shí)這個(gè)workspace也就銷毀了,而flyoutWorkspace宿主是vm,他負(fù)責(zé)將各個(gè)sprite中拖出來的block干掉,因此不能銷毀,另外Blockly.inject的option對(duì)象中可以設(shè)置toolbox選項(xiàng),能夠加載外部xml,而且inject調(diào)用了core/DragSurfaceSvg,這是一個(gè)svg繪圖程序,理論上應(yīng)該也可以在這里渲染block出來,然而他只在這里渲染了兩個(gè)workspace出來,不明白他為什么還要再去渲染一遍),然后為workspace建立blockListener(在vm/engine/block中定義,為block建立的通用(非特定)動(dòng)作函數(shù),如move,delete,create,click什么的,特定的block動(dòng)作函數(shù)的加載參見第八條),然后為flyoutWorkspace建立flyoutBlockListener(追了好久發(fā)現(xiàn)居然和blockListener一毛一樣,move事件(回調(diào)中有parent和input),change事件(回調(diào)中有name和value),所以如果需要給所有的block加統(tǒng)一的事件(僅限field和mutation)可以在這里添加 engine/blocks.js 第294行blocklyListen函數(shù))

ui數(shù)據(jù)在reducer/gui.js中進(jìn)行組裝

guiMiddleware是一個(gè)封裝了throttle的柯里化函數(shù),按照中間件模式調(diào)用,用于為組件節(jié)流(如果一秒內(nèi)點(diǎn)了很多次,只會(huì)執(zhí)行兩次)

index.js是個(gè)假的組裝文件,reducer真正是在lib/app-state-hoc中的AppStateHOC類,這是一個(gè)中間件,在入口函數(shù)render-gui中GUI組件使用compose函數(shù)柯里化封裝 AppStateHOC, HashParserHOC,TitledHOC三個(gè)中間件

AppStateHOC通過判斷是否需要加載paint和gui來加載不同的store,<Provider>就在這個(gè)組件當(dāng)中,返回了經(jīng)過國際化(多語言模塊)和節(jié)流處理的高級(jí)組件(節(jié)流的實(shí)現(xiàn):當(dāng)createStore擁有enhancer函數(shù)時(shí),會(huì)返回一個(gè)enhancer(createStore)(reducer,state)的高級(jí)組件,這樣使用enhancer就可以實(shí)現(xiàn)組件的功能模塊化,類似于鏈?zhǔn)秸{(diào)用(一個(gè)函數(shù)只完成一個(gè)功能))

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末旗吁,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件颊埃,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)补疑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來歹撒,“玉大人莲组,你說我怎么就攤上這事∨玻” “怎么了锹杈?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長迈着。 經(jīng)常有香客問我竭望,道長,這世上最難降的妖魔是什么裕菠? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任咬清,我火速辦了婚禮,結(jié)果婚禮上奴潘,老公的妹妹穿的比我還像新娘旧烧。我一直安慰自己,他們只是感情好画髓,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布掘剪。 她就那樣靜靜地躺著,像睡著了一般奈虾。 火紅的嫁衣襯著肌膚如雪夺谁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天肉微,我揣著相機(jī)與錄音匾鸥,去河邊找鬼。 笑死浪册,一個(gè)胖子當(dāng)著我的面吹牛扫腺,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播村象,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼笆环,長吁一口氣:“原來是場噩夢啊……” “哼攒至!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起躁劣,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤迫吐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后账忘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體志膀,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年鳖擒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了溉浙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蒋荚,死狀恐怖戳稽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情期升,我是刑警寧澤惊奇,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站播赁,受9級(jí)特大地震影響颂郎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜容为,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一乓序、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧舟奠,春花似錦竭缝、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽咙俩。三九已至耿戚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間阿趁,已是汗流浹背膜蛔。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留脖阵,地道東北人皂股。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像命黔,于是被迫代替她去往敵國和親呜呐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子就斤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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

  • scratch軟件的邏輯不復(fù)雜,就是用blockly生成語句塊,然后用虛擬機(jī)抽象成底層語法,最后再調(diào)用render...
    千茉紫依閱讀 4,240評(píng)論 0 12
  • 這是一個(gè)從0搭建一個(gè)scratch項(xiàng)目的demo , 共有14個(gè)分支,每個(gè)分支對(duì)應(yīng)了一個(gè)小的功能 ,完成了一些sr...
    千茉紫依閱讀 2,399評(píng)論 0 1
  • 源碼版本:v2.1.10 分析目標(biāo) 通過閱讀源碼,對(duì) Vue2 的基礎(chǔ)運(yùn)行機(jī)制有所了解蘑辑,主要是: Vue2 中數(shù)據(jù)...
    NARUTO_86閱讀 12,257評(píng)論 6 26
  • 你的強(qiáng)大 是所有人的幸運(yùn) 你的弱小 是自己的悲哀
    虞和蓉閱讀 289評(píng)論 0 4
  • 回鄉(xiāng)聽到少男少女們一樁樁婚姻的短命夭折 心里涌起了別樣的滋味 婚姻似乎失去了其的神圣性 莊嚴(yán)性 責(zé)任性 西方所倡...
    陳糊涂閱讀 241評(píng)論 0 4