日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 前端技术 > javascript >内容正文

javascript

[WebKit] JavaScriptCore解析

發(fā)布時(shí)間:2023/12/19 javascript 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [WebKit] JavaScriptCore解析 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

  • Chakra(Microsoft Internet Explorer)
  • Nitro/JavaScript Core?(Safari)
  • Carakan?(Opera)
  • SpiderMonkey?(Firefox)
  • V8?(Chrome, Chromium)

先看一下官方的基本介紹,短短幾句就塞滿(mǎn)了關(guān)鍵字。

SquirrelFish,正式名稱(chēng)是JavaScriptCore,包括register-based(基于寄存器的虛擬機(jī)), direct-threaded, high-level bytecode engine(字節(jié)碼引擎).它使用基于內(nèi)置copy propagation(復(fù)制性傳播算法)的一次性編譯器(one-pass compiler),能夠延遲從語(yǔ)法樹(shù)(Syntax Tree)上生成字節(jié)碼(Bytecodes)。

由此可見(jiàn)JavaScriptCore實(shí)現(xiàn)的復(fù)雜度。做為一個(gè)正在努力學(xué)習(xí)的菜鳥(niǎo),我愿意給自己這樣一個(gè)挑戰(zhàn),通過(guò)記錄和總結(jié)學(xué)習(xí)內(nèi)容,分成兩個(gè)大的段落從內(nèi)部視角來(lái)解析JavaScriptCore。首先是基礎(chǔ)篇,目的是了解JavaScriptCore是如何與WebKit一起工作的,會(huì)涉及一些JavaScript引擎的一些基本概念。然后是高級(jí)篇,嘗試解釋JavaScriptCore中的核心技術(shù),如Byte Code Compiler, JIT, VM以及GC等。

內(nèi)容或許會(huì)顯得晦澀,如果只是想簡(jiǎn)單地了解瀏覽器JS引擎的一些基本內(nèi)容,推薦讀讀下面的文章。它們對(duì)于理解后面的內(nèi)容也會(huì)非常有幫助。相對(duì)于這些資料,這個(gè)系列則側(cè)重于從基礎(chǔ)及從實(shí)例來(lái)分析JSC的實(shí)現(xiàn)。沒(méi)辦法做到高層次,只能追求貼近實(shí)現(xiàn)。

1、JavaScriptCore, WebKit的JS實(shí)現(xiàn)(一)

2、JavaScriptCore, WebKit的JS實(shí)現(xiàn)(完)

3、為什么V8引擎這么快?

當(dāng)然,JavaScript的知識(shí)是必不可少的,推薦閱讀一篇(JavaScript核心指南),如果有時(shí)間可以深入學(xué)習(xí)一下其中提到的鏈接。

一. JavaScriptCore與WebCore

兩者的關(guān)系可以簡(jiǎn)單的用下圖來(lái)表示:

JSC為WebCore提供兩個(gè)重要功能:

1. JS腳本的解析執(zhí)行 (ScriptController)

主要是通過(guò)調(diào)用JavaScriptCore提供的兩個(gè)C接口來(lái)實(shí)現(xiàn)的, checkSyntax和evaluate.

2. DOM節(jié)點(diǎn)的JS Bindings

DOM節(jié)點(diǎn)所對(duì)應(yīng)的JS Bindings都可以回溯到JSC::JSNonFinalObject,再到JSObject,以實(shí)現(xiàn)和JSC綁定在一起。

*關(guān)于JS Binding,可以先看一下這篇文章: 為JavaScript Binding添加新DOM對(duì)象的三種方式及實(shí)作。至于JSC實(shí)現(xiàn)的細(xì)節(jié),以后再展開(kāi)。

二. JavaScriptCore基本工作過(guò)程

JSC最簡(jiǎn)單的執(zhí)行過(guò)程如下,再如之后JIT等在這個(gè)基礎(chǔ)上的優(yōu)化。

三. JavaScript腳本的執(zhí)行

以下分層說(shuō)明腳本執(zhí)行的步驟。 對(duì)于涉及到編譯及執(zhí)行的細(xì)節(jié),則在后續(xù)解釋。

3.1 接口層的交互

JSC和其它幾個(gè)主要的JS Engine一樣,都是一個(gè)庫(kù),通過(guò)提供簡(jiǎn)單的API來(lái)供調(diào)用者使用。

從JSC接口來(lái)看,一個(gè)完整的JavaScript腳本的解析執(zhí)行過(guò)程,可以概述以下:

過(guò)程很簡(jiǎn)單,可是很明顯有些關(guān)鍵詞必須要理解一下,如VM, Global Object, ExecState. 它們的關(guān)系也可以通過(guò)一張圖來(lái)解釋:

VM -> Virtual Machine, JavaScript要借助于一個(gè)運(yùn)行時(shí)(Runtime)環(huán)境來(lái)運(yùn)行。 SpiderMonkey就稱(chēng)之為Runtime.

GlobalObject -> 腳本執(zhí)行時(shí)的全局對(duì)象。一個(gè)全局負(fù)責(zé)組織管理執(zhí)行環(huán)境以及各個(gè)子對(duì)象。

ExecState -> 用于記錄腳本執(zhí)行上下文或環(huán)境, 也由GlobalObject管理。SpiderMoney以及Apple封裝后的JavaScriptCore.framework都稱(chēng)之為上下文(Context). 可以將其視為一個(gè)執(zhí)行腳本的對(duì)象來(lái)理解,只是它所產(chǎn)生和使用的Objects是共享的,并可以由GlobalObject來(lái)訪問(wèn)。

JS解釋器各自實(shí)現(xiàn)的方式略有不同,JSC是由一個(gè)全局變量(Global Object)來(lái)創(chuàng)建上下文環(huán)境(ExecState), 而SpiderMonkey則是由執(zhí)行上下文來(lái)創(chuàng)建全局變量。但無(wú)論哪種實(shí)現(xiàn),全局變量和上下文都一一對(duì)應(yīng)的,雖然原則上是允許一對(duì)多的情況出現(xiàn)。

最后看下JSC執(zhí)行JS腳本的接口定義, 就很好理解了:

JSValue evaluate(ExecState* exec,constSourceCode& source,JSValue thisValue,JSValue* returnedException);

*thisValue就是JavaScript的this, 代表的是執(zhí)行者, 但不一定是創(chuàng)建者。

*使用JSC的示例代碼,可以看看WebKit里的jsc.cpp就可以了。

*如果覺(jué)得沒(méi)有講清楚,建議讀讀這里(JavaScript核心指南)。

3.2 JSC API執(zhí)行腳本的步驟

下圖是JSC API函數(shù)evaluate的活動(dòng)圖:

重點(diǎn)在于它會(huì)使用要執(zhí)行的腳本內(nèi)容建立一個(gè)ProgramExecutable對(duì)象,然后調(diào)用Interpreter執(zhí)行這個(gè)代表腳本的ProgramExecutable對(duì)象。

ProgramExecutable和Interpreter都是JSC核心類(lèi),ProgramExecutable負(fù)責(zé)編譯代碼為ByteCode,屬于解釋器功能組, 而Interpreter則負(fù)責(zé)解析執(zhí)行ByteCode,則屬于VM功能組.

*Interpreter提供的兩個(gè)dump函數(shù)對(duì)于分析代碼也很有用, dumpCallFrame和dumpRegisters。

四. DOM Bindings的響應(yīng)

實(shí)現(xiàn)上的解析在這里:WebKit的JS Binding解析

以及另一篇可以加深理解:為JavaScript Binding添加新DOM對(duì)象的三種方式及實(shí)作

這一篇主要說(shuō)明解釋器的基本工作過(guò)程和JSC的核心組件的實(shí)現(xiàn)。

作為一個(gè)語(yǔ)言,就像人在的平時(shí)交流時(shí)一樣,當(dāng)接收到信息后,包含兩個(gè)過(guò)程:先理解再行動(dòng)。理解的過(guò)程就是語(yǔ)言解析的過(guò)程,行動(dòng)就是根據(jù)解析的結(jié)果執(zhí)行對(duì)應(yīng)的行為。在計(jì)算機(jī)領(lǐng)域,理解就是編譯或解釋,這個(gè)已經(jīng)被研究的很透徹了,并且有了工具來(lái)輔助。而執(zhí)行則千變?nèi)f化,也是性能優(yōu)化的重心。下面就來(lái)看看JSC是如何來(lái)理解、執(zhí)行JavaScript腳本的。

解釋器工作過(guò)程

JavaScriptCore基本的工作過(guò)程如下:

對(duì)于一個(gè)解釋器,首先必須要明確所支持的語(yǔ)言, JSC所支持的是EMCAScript-262規(guī)范。

詞法分析和語(yǔ)法分析就是理解的過(guò)程,將輸入的文本轉(zhuǎn)為一種它可以理解的語(yǔ)義形式(抽象語(yǔ)法樹(shù)), 或者更進(jìn)一步的生成供后續(xù)使用的中間代碼(字節(jié)碼,ByteCode)。

解釋器就是負(fù)責(zé)執(zhí)行解析輸出的結(jié)果。正因?yàn)閳?zhí)行是優(yōu)化的重心,所以有JIT來(lái)提高執(zhí)行效能。根據(jù)資料,V8還會(huì)優(yōu)化Parser的輸出,省去了bytecode, 當(dāng)解釋器有能力直接基于AST執(zhí)行。

詞法分析及語(yǔ)法分析,最著名的工具就是lex/yacc,以及后繼者flex/bison(The LEX&YACC Page)。它們?yōu)楹芏嘬浖峁┝苏Z(yǔ)言或文本解析的功能,相當(dāng)強(qiáng)大,也很有趣。雖然JavaScriptCore并沒(méi)有使用它們,而是自行編寫(xiě)實(shí)現(xiàn)的,但基本思路是相似的。

詞法分析(lexer),其實(shí)就是一個(gè)掃描器,依據(jù)語(yǔ)言的定義,提取出源文件中的內(nèi)容變?yōu)橐粋€(gè)個(gè)語(yǔ)法可以識(shí)別的token,比如關(guān)鍵字,操作符,常量等。在一個(gè)文件中定義好規(guī)則就可以了。

語(yǔ)法分析(paser), 它的功能就是根據(jù)語(yǔ)法(token的順序組合),識(shí)別出不同的語(yǔ)義(目標(biāo)操作)。

比如:

i=3;

經(jīng)過(guò)lexer可能被識(shí)別為以下的tokens:

VARIABLE EQUAL CONSTANT END

經(jīng)過(guò)parser一分析,就了解這是一個(gè)"賦值操作,向變量i賦值常量3"。隨后再調(diào)用對(duì)應(yīng)的操作加以執(zhí)行。

如果你對(duì)lexer和parser還不太熟悉,可參考的資料很多,這里有一個(gè)基本的入門(mén)指引:Yacc與Lex快速入門(mén)。

執(zhí)行的基礎(chǔ)環(huán)境(Register-based VM)

JSC解析生成的代碼放到一個(gè)虛擬機(jī)上來(lái)執(zhí)行(廣義上講JSC主身就是一個(gè)虛擬機(jī))。JSC使用的是一個(gè)基于寄存器的虛擬機(jī)(register-based VM),另一種實(shí)現(xiàn)方式是基于棧的虛擬機(jī)(stack-based VM)。兩者的差異可以簡(jiǎn)單的理解為指令集傳遞參數(shù)的方式,是使用寄存器,還是使用棧。

相對(duì)于基于棧的虛擬機(jī),因?yàn)椴恍枰l繁的壓、出棧,以及對(duì)三元操作的支持,register-based VM的效率更高,但可移植性相對(duì)弱一些。所謂的三元操作符,其中add就是一個(gè)三元操作,add dst, src1, src2,功能是將src1與src2相加,將結(jié)果保存在dst中。dst, src1,src2都是寄存器。

為了方便和<<深入理解Java虛擬機(jī)>>中的示例進(jìn)行對(duì)比,也利用JSC輸出以下腳本的ByteCode如下:

[ 0] enter [ 1] mov r0, Cell: 0133FC40(@k0) [ 4] put_by_id r0, a(@id0), Int32: 100(@k1) [ 13] mov r0, Cell: 0133FC40(@k0) [ 16] put_by_id r0, b(@id1), Int32: 200(@k2) [ 25] mov r0, Cell: 0133FC40(@k0) [ 28] put_by_id r0, c(@id2), Int32: 300(@k3) [ 37] resolve_global r0, a(@id0) [ 43] resolve_global r1, b(@id1) [ 49] add r0, r0, r1 [ 54] resolve_global r1, c(@id2) [ 60] mul r0, r0, r1 [ 65] ret r0

*參考: JSC字節(jié)碼規(guī)格 (WebKit沒(méi)有及時(shí)更新,只做為參考,最新的內(nèi)容還是要看代碼.)

而基于棧的虛擬機(jī)的生成的字節(jié)碼如下:

0: bipush 100 2: istore_1 3: sipush 200 6: istore_2 7: sipush 300 10: istore_3 11: iload_1 12: iload_2 13: iadd 14: iload_3 15: imul 16: ireturn

可以幫助理解它們之間的差異。

核心組件

*這部分基本上譯自WebKit官網(wǎng)的JavaScriptCore說(shuō)明的前半部分。

JavaScriptCore?是一個(gè)正在演進(jìn)的虛擬機(jī)(virtual machine), 包含了以下模塊: lexer, parser, start-up interpreter (LLInt), baseline JIT, and an optimizing JIT (DFG).

Lexer?負(fù)責(zé)詞法解析(lexical analysis) , 就是將腳本分解為一系列的tokens. JavaScriptCore的 lexer是手動(dòng)撰寫(xiě)的,大部分代碼在parser/Lexer.h 和 parser/Lexer.cpp 中.

Parser?處理語(yǔ)法分析(syntactic analysis), 也就是基于來(lái)自Lexer的tokens創(chuàng)建語(yǔ)法樹(shù)(syntax tree). JavaScriptCore 使用的是一個(gè)手動(dòng)編寫(xiě)的遞歸下降解析器(recursive descent parser), 代碼位于parser/JSParser.h 和 parser/JSParser.cpp .

LLInt,?全稱(chēng)為L(zhǎng)ow Level Interpreter, 負(fù)責(zé)執(zhí)行由Paser生成的字節(jié)碼(bytecodes). 代碼在llint/ 目錄里, 使用一個(gè)可移植的匯編實(shí)現(xiàn),也被為offlineasm (代碼在offlineasm/目錄下), 它可以編譯為x86和ARMv7的匯編以及C代碼。LLInt除了詞法解析和語(yǔ)法解釋外,JIT編譯器所執(zhí)行的調(diào)用、棧、以及寄存器轉(zhuǎn)換都是基本沒(méi)有啟動(dòng)開(kāi)銷(xiāo)(start-up cost)的。比如,調(diào)用一個(gè)LLInt函數(shù)就和調(diào)用一個(gè)已經(jīng)被編譯原始代碼的函數(shù)相似, 除非機(jī)器碼的入口正是一個(gè)共用的LLInt Prologue(公共函數(shù)頭,shared LLInt prologue). LLInt還包括了一些優(yōu)化,比如使用inline cacheing來(lái)加速屬性訪問(wèn).

Baseline JIT?在函數(shù)被調(diào)用了6次,或者某段代碼循環(huán)了100次后(也可能是一些組合,比如3次帶有50次枚舉的調(diào)用)就會(huì)觸發(fā)Baseline JIT。這些數(shù)字只是大概的估計(jì),實(shí)際上的啟發(fā)(heuristics)過(guò)程是依賴(lài)于函數(shù)大小和當(dāng)時(shí)內(nèi)存狀況的。當(dāng)JIT卡在一個(gè)循環(huán)時(shí),它會(huì)執(zhí)行On-Stack-Replace(OSR)將函數(shù)的所有調(diào)用者重新指向新的編譯代碼。Baseline JIT同時(shí)也是函數(shù)進(jìn)一步優(yōu)化的后備,如果無(wú)法優(yōu)化代碼時(shí),它還會(huì)通過(guò)OSR調(diào)整到Baseline JIT. BaseLine JIT的代碼在 jit/ . 基線JIT也為inline caching執(zhí)行幾乎所有的堆訪問(wèn)。

無(wú)論是LLInt和Baseline JIT者會(huì)收集一些輕量級(jí)的性能信息,以便擇機(jī)到更高一層級(jí)(DFG)執(zhí)行。收集的信息包括最近從參數(shù)、堆,以及返回值中的數(shù)據(jù)。另外,所有inline caching也做了些處理,以方便DFG進(jìn)行類(lèi)型判斷,例如,通過(guò)查詢(xún)inline cache的狀態(tài),可以檢測(cè)到使用特定類(lèi)理進(jìn)行堆訪問(wèn)的頻率。這個(gè)可以用于決定是否進(jìn)入DFG (文中稱(chēng)這個(gè)行為叫speculation, 有點(diǎn)賭一把的意思,能優(yōu)化獲得更高的性能最好,不然就退回來(lái))。在下一節(jié)中著重講述JavaScriptCore類(lèi)型推斷。

DFG JIT?在函數(shù)被調(diào)用了至少60次,或者代碼循環(huán)了1000次,就會(huì)觸發(fā)DFG JIT。同樣,這些都是近似數(shù),整個(gè)過(guò)程也是趨向于啟發(fā)式的。DFG積極地基于前面(baseline JIT&Interpreter)收集的數(shù)據(jù)進(jìn)行類(lèi)型推測(cè),這樣就可以盡早獲得類(lèi)型信息(forward-propagate type information),從而減少了大量的類(lèi)型檢查。DFG也會(huì)自行進(jìn)行推測(cè),比如為了啟用inlining, 可能會(huì)將從heap中加載的內(nèi)容識(shí)別出一個(gè)已知的函數(shù)對(duì)象。如果推測(cè)失敗,DFG取消優(yōu)化(Deoptimization),也稱(chēng)為"OSR exit". Deoptimization可能是同步的(某個(gè)類(lèi)型檢測(cè)分支正在執(zhí)行),也可能是異步的(比如runtime觀察到某個(gè)值變化了,并且與DFG的假設(shè)是沖突的),后者也被稱(chēng)為"watchpointing"。 Baseline JIT和DFG JIT共用一個(gè)雙向的OSR:Baseline可以在一個(gè)函數(shù)被頻繁調(diào)用時(shí)OSR進(jìn)入DFG, 而DFG則會(huì)在deoptimization時(shí)OSR回到Baseline JIT. 反復(fù)的OSR退出(OSR exits)還有一個(gè)統(tǒng)計(jì)功能: DFG OSR退出會(huì)像記錄發(fā)生頻率一樣記錄下退出的理由(比如對(duì)值的類(lèi)型推測(cè)失敗), 如果退出一定次數(shù)后,就會(huì)引發(fā)重新優(yōu)化(reoptimization), 函數(shù)的調(diào)用者會(huì)重新被定位到Baseline JIT,然后會(huì)收集更多的統(tǒng)計(jì)信息,也許根據(jù)需要再次調(diào)用DFG。重新優(yōu)化使用了指數(shù)式的回退策略(exponential back-off,會(huì)越來(lái)越來(lái))來(lái)應(yīng)對(duì)一些奇葩代碼。DFG代碼在dfg/.

任何時(shí)候,函數(shù), eval代碼塊,以及全局代碼(global code)都可能會(huì)由LLInt, Baseline JIT和DFG三者同時(shí)運(yùn)行。一個(gè)極端的例子是遞歸函數(shù),因?yàn)橛卸鄠€(gè)stack frames,就可能一個(gè)運(yùn)行在LLInt下,另一個(gè)運(yùn)行在Baseline JIT里,其它的可能正運(yùn)行在DFG里。更為極端的情況是當(dāng)重新優(yōu)化在執(zhí)行過(guò)程被觸發(fā)時(shí),就會(huì)出現(xiàn)一個(gè)stack frame正在執(zhí)行原來(lái)舊的DFG編譯,而另一個(gè)則正執(zhí)行新的DFG編譯。為此三者設(shè)計(jì)成維護(hù)相同的執(zhí)行語(yǔ)義(execution semantics), 它們的混合使用也是為了帶來(lái)明顯的效能提升。

*如果想要觀察它們的工作,可以在WebKit中的子工程jsc的jsc.cpp中,使用JSC::Options添加一部分log輸出。

前面說(shuō)了一些解析、生成ByteCode直至JIT的基本概念,下面是對(duì)照J(rèn)avaScriptCore源代碼來(lái)大致了解它的實(shí)現(xiàn)。

從JS Script到Byte Code

首先說(shuō)明Lexer, Parser和ByteCode的生成都是由ProgramExecutable初始化過(guò)程完成的。首先在JSC的API evaluate()中會(huì)創(chuàng)建ProgramExecutable并指定腳本代碼。然后傳入Interpreter時(shí),再透過(guò)CodeCache獲取的UnlinkedProgramCodeBlock就是已經(jīng)生成ByteCode后的Code Block了。

下圖是CodeCache調(diào)用Parser和ByteCodeGenerator的序列圖:

而Lexer則是在Parser過(guò)程中調(diào)用的,如下圖:

再?gòu)念?lèi)圖來(lái)觀察所涉及的幾個(gè)類(lèi)之間的關(guān)系:

關(guān)于CodeBlock、UnlinkedCodeBlock和ScriptExecutable

CodeBlock可以理解為代碼管理的類(lèi),按類(lèi)型分為GlobalCodeBlock, ProgramCodeBlock, FunctionCodeBlock及EvalCodeBlock, 與之對(duì)應(yīng)的UnlinkedCodeBlock和ScriptExecutable也有相似的繼承體系,如下所示:

UnlinkedCodeBlock存儲(chǔ)的是編譯后的ByteCode,而CodeBlock則會(huì)用于LLint和JIT。

ProgramExecutable則可以理解為當(dāng)前所執(zhí)行腳本的大總管,從其名字上可以看出來(lái)是代表一個(gè)可執(zhí)行程序。

它們的作用也很容易理解。

關(guān)于LLint的slow path

前面說(shuō)過(guò)了LLint是基于offlineasm的匯編語(yǔ)言,這里只是介紹一下它的slow path. 為了處理一些操作,需要在LLint執(zhí)行指令時(shí)調(diào)用一些C函數(shù)進(jìn)行擴(kuò)展處理,比如后面要說(shuō)明的JIT統(tǒng)計(jì)功能,LLint提供一個(gè)調(diào)用C函數(shù)的接口,并將所有會(huì)被調(diào)用的C函數(shù)稱(chēng)為slow path,如下圖所示:

代碼可以在LowLevelInterpreterXXX.asm中看到。所以可以C函數(shù)聲明看到帶有SLOW_PATH的宏。

關(guān)于JIT優(yōu)化的觸發(fā)

首先JSC使用的是基于計(jì)數(shù)器的熱點(diǎn)探測(cè)方法。前面提到函數(shù)或循環(huán)體被執(zhí)行若干次后會(huì)觸發(fā)JIT, 首先這個(gè)次數(shù)是可以通過(guò)JSC::Options中的thresholdForOptimizeSoon來(lái)設(shè)定的。然后在LLint在執(zhí)行循環(huán)的ByteCode指令loop_hint和函數(shù)返回指令ret時(shí)會(huì)調(diào)用slow path中的C函數(shù),進(jìn)行次數(shù)統(tǒng)計(jì)和判斷,過(guò)程如下:

其中會(huì)根據(jù)checkIfJITThresholdReached()返回結(jié)果來(lái)決定是否進(jìn)行jitCompile.一旦要進(jìn)行JIT編譯時(shí),也是根據(jù)當(dāng)前CodeBlock的類(lèi)型,而執(zhí)行針對(duì)不同函數(shù)或代碼段的優(yōu)化。下面顯示的是對(duì)一個(gè)頻繁使用的函數(shù)進(jìn)行JIT編譯的操作:

其中計(jì)數(shù)的功能并非由CodeBlock直接實(shí)現(xiàn),而是通過(guò)ExecutionCounter來(lái)管理的。主要關(guān)系如下:

總結(jié)

以上是生活随笔為你收集整理的[WebKit] JavaScriptCore解析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。