<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          簡(jiǎn)單、好懂的Svelte實(shí)現(xiàn)原理

          共 6408字,需瀏覽 13分鐘

           ·

          2021-11-16 07:45


          作者:卡頌
          簡(jiǎn)介:《React技術(shù)揭秘》作者




          Svelte問(wèn)世很久了,一直想寫一篇好懂的原理分析文章,拖了這么久終于寫了。
          本文會(huì)圍繞一張流程圖和兩個(gè)Demo講解,正確的食用方式是用電腦打開本文,跟著流程圖、Demo一邊看、一邊敲、一邊學(xué)。

          讓我們開始吧!

          Demo1


          Svelte的實(shí)現(xiàn)原理:


          圖中Component是開發(fā)者編寫的組件,內(nèi)部虛線部分是由Svelte編譯器編譯而成的。圖中的各個(gè)箭頭是運(yùn)行時(shí)的工作流程。

          首先來(lái)看編譯時(shí),考慮如下App組件代碼:
          <h1>{count}</h1>
          <script>
            let count = 0;
          </script>

          完整代碼地址https://svelte.dev/repl/9945d189204a4168b4c23890f1d92a3a?version=3.19.1

          瀏覽器顯示:


          這段代碼經(jīng)由編譯器編譯后產(chǎn)生如下代碼,包括三部分:

          • create_fragment方法
          • count的聲明語(yǔ)句
          • class App的聲明語(yǔ)句

          // 省略部分代碼…
          function create_fragment(ctx) {
            let h1;

            return {
              c() {
                h1 = element("h1");
                h1.textContent = `${count}`;
              },
              m(target, anchor) {
                insert(target, h1, anchor);
              },
              d(detaching) {
                if (detaching) detach(h1);
              }
            };
          }

          let count = 0;

          class App extends SvelteComponent {
            constructor(options) {
              super();
              init(this, options, null, create_fragment, safe_not_equal, {});
            }
          }

          export default App;


          create_fragment


          首先來(lái)看create_fragment方法,他是編譯器根據(jù)App的UI編譯而成,提供該組件與瀏覽器交互的方法,在上述編譯結(jié)果中,包含3個(gè)方法:

          • c,代表create,用于根據(jù)模版內(nèi)容,創(chuàng)建對(duì)應(yīng)DOM Element。例子中創(chuàng)建H1對(duì)應(yīng)DOM Element:

          h1 = element("h1");
          h1.textContent = `${count}`;


          • m,代表mount,用于將c創(chuàng)建的DOM Element插入頁(yè)面,完成組件首次渲染。例子中會(huì)將H1插入頁(yè)面:

          insert(target, h1, anchor);
          insert方法會(huì)調(diào)用target.insertBefore:
          function insert(target, node, anchor) {
            target.insertBefore(node, anchor || null);
          }

          • d,代表detach,用于將組件對(duì)應(yīng)DOM Element從頁(yè)面中移除。例子中會(huì)移除H1:

          if (detaching) detach(h1);
          detach方法會(huì)調(diào)用parentNode.removeChild:
          function detach(node) {
            node.parentNode.removeChild(node);
          }

          仔細(xì)觀察流程圖,會(huì)發(fā)現(xiàn)App組件編譯的產(chǎn)物沒(méi)有圖中fragment內(nèi)的p方法。

          這是因?yàn)锳pp沒(méi)有變化狀態(tài)的邏輯,所以相應(yīng)方法不會(huì)出現(xiàn)在編譯產(chǎn)物中。
          可以發(fā)現(xiàn),create_fragment返回的c、m方法用于組件首次渲染。那么是誰(shuí)調(diào)用這些方法呢?



          SvelteComponent


          每個(gè)組件對(duì)應(yīng)一個(gè)繼承自SvelteComponent的class,實(shí)例化時(shí)會(huì)調(diào)用init方法完成組件初始化,create_fragment會(huì)在init中調(diào)用:
          class App extends SvelteComponent {
            constructor(options) {
              super();
              init(this, options, null, create_fragment, safe_not_equal, {});
            }
          }

          總結(jié)一下,流程圖中虛線部分在Demo1中的編譯結(jié)果為:

          • fragment:編譯為create_fragment方法的返回值

          • UI:create_fragment返回值中m方法的執(zhí)行結(jié)果

          • ctx:代表組件的上下文,由于例子中只包含一個(gè)不會(huì)改變的狀態(tài)count,所以ctx就是count的聲明語(yǔ)句


          可以改變狀態(tài)的Demo


          現(xiàn)在修改Demo,增加update方法,為H1綁定點(diǎn)擊事件,點(diǎn)擊后count改變:
          <h1 on:click="{update}">{count}</h1>

          <script>
            let count = 0;
            function update() {
              count++;
            }
          </script>

          完整代碼地址:https://svelte.dev/repl/bf22a31a0eff4875b5b3084aa2b85fc3?version=3.19.1

          編譯產(chǎn)物發(fā)生變化,ctx的變化如下:
          // 從module頂層的聲明語(yǔ)句
          let count = 0;

          // 變?yōu)閕nstance方法
          function instance($$self, $$props, $$invalidate) {
            let count = 0;

            function update() {
              $$invalidate(0, count++, count);
            }

            return [count, update];
          }

          count從module頂層的聲明語(yǔ)句變?yōu)閕nstance方法內(nèi)的變量。之所以產(chǎn)生如此變化是因?yàn)锳pp可以實(shí)例化多個(gè):
          // 模版中定義3個(gè)App
          <App/>
          <App/>
          <App/>

          // 當(dāng)count不可變時(shí),頁(yè)面渲染為:<h1>0</h1>
          <h1>0</h1>
          <h1>0</h1>

          當(dāng)count不可變時(shí),所有App可以復(fù)用同一個(gè)count。但是當(dāng)count可變時(shí),根據(jù)不同App被點(diǎn)擊次數(shù)不同,頁(yè)面可能渲染為:
          <h1>0</h1>
          <h1>3</h1>
          <h1>1</h1>

          所以每個(gè)App需要有獨(dú)立的上下文保存count,這就是instance方法的意義。

          推廣來(lái)說(shuō),Svelte編譯器會(huì)追蹤<script>內(nèi)所有變量聲明:

          • 是否包含改變?cè)撟兞康恼Z(yǔ)句,比如count++
          • 是否包含重新賦值的語(yǔ)句,比如count = 1
          • 等等情況

          一旦發(fā)現(xiàn),就會(huì)將該變量提取到instance中,instance執(zhí)行后的返回值就是組件對(duì)應(yīng)ctx。

          同時(shí),如果執(zhí)行如上操作的語(yǔ)句可以通過(guò)模版被引用,則該語(yǔ)句會(huì)被$$invalidate包裹。

          在Demo2中,update方法滿足:

          • 包含改變count的語(yǔ)句 —— count++
          • 可以通過(guò)模版被引用 —— 作為點(diǎn)擊回調(diào)函數(shù)

          所以編譯后的update內(nèi)改變count的語(yǔ)句被$$invalidate方法包裹:
          // 源代碼中的update
          function update() {
            count++;
          }

          // 編譯后instance中的update
          function update() {
            $$invalidate(0, count++, count);
          }

          從流程圖可知,$$invalidate方法會(huì)執(zhí)行如下操作:

          • 更新ctx中保存狀態(tài)的值,比如Demo2中count++

          • 標(biāo)記dirty,即標(biāo)記App UI中所有和count相關(guān)的部分將會(huì)發(fā)生變化

          • 調(diào)度更新,在microtask中調(diào)度本次更新,所有在同一個(gè)macrotask中執(zhí)行的$$invalidate都會(huì)在該macrotask執(zhí)行完成后被統(tǒng)一執(zhí)行,最終會(huì)執(zhí)行組件fragment中的p方法



          p方法是Demo2中新的編譯產(chǎn)物,除了p之外,create_fragment已有的方法也產(chǎn)生相應(yīng)變化:
          c() {
            h1 = element("h1");
            // count的值變?yōu)閺腸tx中獲取
            t = text(/*count*/ ctx[0]);
          },
          m(target, anchor) {
            insert(target, h1, anchor);
            append(h1, t);
            // 事件綁定
            dispose = listen(h1, "click", /*update*/ ctx[1]);
          },
          p(ctx, [dirty]) {
            // set_data會(huì)更新t保存的文本節(jié)點(diǎn)
            if (dirty & /*count*/ 1) set_data(t, /*count*/ ctx[0]);
          },
          d(detaching) {
            if (detaching) detach(h1);
            // 事件解綁
            dispose();
          }

          p方法會(huì)執(zhí)行$$invalidate中標(biāo)記為dirty的項(xiàng)對(duì)應(yīng)的更新函數(shù)。

          在Demo2中,App UI中只引用了狀態(tài)count,所以u(píng)pdate方法中只有一個(gè)if語(yǔ)句,如果UI中引用了多個(gè)狀態(tài),則p方法中也會(huì)包含多個(gè)if語(yǔ)句:
          // UI中引用多個(gè)狀態(tài) 
          <h1 on:click="{count0++}">{count0}</h1>
          <h1 on:click="{count1++}">{count1}</h1>
          <h1 on:click="{count2++}">{count2}</h1>

          對(duì)應(yīng)p方法包含多個(gè)if語(yǔ)句:
          p(new_ctx, [dirty]) {
            ctx = new_ctx;
            if (dirty & /*count*/ 1) set_data(t0, /*count*/ ctx[0]);
            if (dirty & /*count1*/ 2) set_data(t2, /*count1*/ ctx[1]);
            if (dirty & /*count2*/ 4) set_data(t4, /*count2*/ ctx[2]);
          },

          Demo2完整的更新步驟如下:

          • 點(diǎn)擊H1觸發(fā)回調(diào)函數(shù)update

          • update內(nèi)調(diào)用$$invalidate,更新ctx中的count,標(biāo)記count為dirty,調(diào)度更新

          • 執(zhí)行p方法,進(jìn)入dirty的項(xiàng)(即count)對(duì)應(yīng)if語(yǔ)句,執(zhí)行更新對(duì)應(yīng)DOM Element的方法


          總結(jié)


          Svelte的完整工作流程會(huì)復(fù)雜的多,但是核心實(shí)現(xiàn)便是如此。

          我們可以直觀的感受到,借由模版語(yǔ)法的約束,經(jīng)過(guò)編譯優(yōu)化,可以直接建立狀態(tài)與要改變的DOM節(jié)點(diǎn)的對(duì)應(yīng)關(guān)系。

          在Demo2中,狀態(tài)count的變化直接對(duì)應(yīng)p方法中一個(gè)if語(yǔ)句,使得Svelte執(zhí)行細(xì)粒度的更新時(shí)對(duì)比使用虛擬DOM的框架更有性能優(yōu)勢(shì)。


          上述性能分析中第四行select row就是一個(gè)細(xì)粒度的更新。想比較之下,React(倒數(shù)第三列)性能就差很多。



          點(diǎn)擊左下角閱讀原文,到 SegmentFault 思否社區(qū) 和文章作者展開更多互動(dòng)和交流,掃描下方”二維碼“或在“公眾號(hào)后臺(tái)回復(fù)“ 入群 ”即可加入我們的技術(shù)交流群,收獲更多的技術(shù)文章~

          - END -


          瀏覽 110
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  wwwav在线观看 | 欧美日韩国产高清 | 男人操女人网站 | 日韩黄色无码视频 | 成人网站无人区一区二区三区 |