svelte響應式原理

来源:https://www.cnblogs.com/heavenYJJ/archive/2023/12/28/17933469.html
-Advertisement-
Play Games

svelte文件編譯為js後的結構 源代碼: <script lang="ts"> let firstName = '張' let lastName = '三' let age = 18 function handleChangeName() { firstName = '王' lastName = ...


svelte文件編譯為js後的結構

源代碼:

  <script lang="ts">
    let firstName = '張'
    let lastName = '三'
    let age = 18

    function handleChangeName() {
      firstName = '王'
      lastName = '二'
    }

    function handleChangeAge() {
      age = 28
    }
  </script>

  <div>
    <p>fullName is {firstName} {lastName}</p>
    <p>age is {age}</p>
    <div>
      <button on:click={handleChangeName}>change name</button>
      <button on:click={handleChangeAge}>change age</button>
    </div>
  </div>

編譯後的js代碼結構

  function create_fragment(ctx) {
  	const block = {
  		c: function create() {
  			// ...
  		},
  		m: function mount(target, anchor) {
  			// ...
  		},
  		p: function update(ctx, [dirty]) {
  			// ...
  		},
  		d: function destroy(detaching) {
  			// ...
  		}
  	};
  	return block;
  }

  function instance($$self, $$props, $$invalidate) {
  	let firstName = '張';
  	let lastName = '三';
  	let age = 18;

  	function handleChangeName() {
  		$$invalidate(0, firstName = '王');
  		$$invalidate(1, lastName = '二');
  	}

  	function handleChangeAge() {
  		$$invalidate(2, age = 28);
  	}


  	return [firstName, lastName, age, handleChangeName, handleChangeAge];
  }

  class Name extends SvelteComponentDev {
  	constructor(options) {
  		init(this, options, instance, create_fragment, safe_not_equal, {});
  	}
  }

初始化調用init方法

  function init(component, options, instance, create_fragment, ...,dirty = [-1]) {
  	// $$屬性為組件的實例
  	const $$ = component.$$ = {
      	...
          // dirty的作用是標記哪些變數需要更新,
          // 在update生命周期的時候將那些標記的變數和對應的dom找出來,更新成最新的值。
          dirty,
          // fragment欄位為一個對象,對象裡面有create、mount、update等方法
          fragment: null,
          // 實例的ctx屬性是個數組,存的是組件內的頂層變數、方法等。按照定義的順序存儲
          ctx: [],
          ...
      }

      // ctx屬性的值為instance方法的返回值。
      // instance方法就是svelte文件編譯script標簽代碼生成的。
      // instance方法的第三個參數為名字叫$$invalidate的箭頭函數,
      // 在js中修改變數的時候就會自動調用這個方法
      $$.ctx = instance
  		? instance(component, options.props || {}, (i, ret, ...rest) => {
  			const value = rest.length ? rest[0] : ret;
  			if ($$.ctx && not_equal($$.ctx[i], $$.ctx[i] = value)) {
  				make_dirty(component, i);
  			}
  			return ret;
  		})
  		: [];

      // 調用create_fragment方法
      // 並且在後續對應的生命周期裡面調用create_fragment方法返回的create、mount、update等方法
      $$.fragment = create_fragment ? create_fragment($$.ctx) : false;
  }

點擊change name按鈕,修改firstName和lastName的值

  let firstName = '張'
  let lastName = '三'
  let age = 18

  function handleChangeName() {
  	// firstName變數第一個定義,所以這裡是0,並且將新的firstName的值傳入$$invalidate方法
  	$$invalidate(0, firstName = '王');
      // lastName變數第二個定義,所以這裡是1,並且將新的firstName的值傳入$$invalidate方法
  	$$invalidate(1, lastName = '二');
  }

  // ...

再來看看invalidate函數的定義,invalidate函數就是在init時調用instance的時候傳入的第三個參數

  (i, ret, ...rest) => {
  	// 拿到更新後的值
  	const value = rest.length ? rest[0] : ret;
      // 判斷更新前和更新後的值是否相等,不等就調用make_dirty方法
  	if ($$.ctx && not_equal($$.ctx[i], $$.ctx[i] = value)) {
      	// 第一個參數為組件對象,第二個參數為變數的index。
          // 當更新的是firstName變數,firstName是第一個定義的,所以這裡的i等於0
          // 當更新的是lastName變數,lastName是第二個定義的,所以這裡的i等於1
  		make_dirty(component, i);
  	}
  	return ret;
  }

make_dirty方法的定義

  function make_dirty(component, i) {
  	// dirty初始化的時候是由-1組成的數組,dirty[0] === -1說明是第一次調用make_dirty方法。
  	if (component.$$.dirty[0] === -1) {
  		dirty_components.push(component);
          // 在下一個微任務中調用create_fragment方法生成對象中的update方法。
  		schedule_update();
          // 將dirty數組的值全部fill為0
  		component.$$.dirty.fill(0);
  	}
  	component.$$.dirty[(i / 31) | 0] |= (1 << (i % 31));
  }

二進位運算 demo

  // 有採購商許可權
  purchaser= 1 << 2 => 100
  // 有供應商商許可權
  supplier = 1 << 1 => 010
  // 有運營許可權
  admin =    1 << 0 => 001

  user1 = purchaser | supplier | admin => 111
  user2 = purchaser | supplier => 110

  // 用戶是否有admin的許可權
  user1 & admin = 111 & 001 = true
  user2 & admin = 110 & 001 = false

再來看看component.$$.dirty[(i / 31) | 0] |= (1 << (i % 31));。dirty數組中每一位能夠標記31個變數是否為dirty。

(i / 31) | 0就是i/31然後取整。

  • 比如i=0,計算結果為0。
  • i=1,計算結果為0。
  • i=32,計算結果為1。

(1 << (i % 31)),1左移的位數為i和31求餘的值。

  • 比如i=0,計算結果為1<<0 => 01。
  • i=1,計算結果為1 << 1 => 10。
  • i=32,計算結果為1<<1 => 10。

當i=0時這行代碼就變成了component.$$.dirty[0] |= 01,由於dirty數組在前面已經被fill為0了,所以代碼就變成了component.$$.dirty[0] = 0 | 01 => component.$$.dirty[0] = 01。說明從右邊數第一個變數被標記為dirty。

同理當i=1時這行代碼就變成了component.$$.dirty[0] |= 10 =>component.$$.dirty[0] = 0 | 10 => component.$$.dirty[0] = 10。說明從右邊數第二個變數被標記為dirty。

create_fragment函數

function create_fragment(ctx) {
  let div1;
  let p0;
  let t0;
  let t1;
  let t2;
  let t3;
  let t4;
  let p1;
  let t5;
  let t6;
  let t7;
  let div0;
  let button0;
  let t9;
  let button1;
  let mounted;
  let dispose;

  const block = {
    // create生命周期時調用,調用瀏覽器的dom方法生成對應的dom。
    // element、text這些方法就是瀏覽器的
    // document.createElement、document.createTextNode這些原生方法
    c: function create() {
      div1 = element("div");
      p0 = element("p");
      t0 = text("fullName is ");
      t1 = text(/*firstName*/ ctx[0]);
      t2 = space();
      t3 = text(/*lastName*/ ctx[1]);
      t4 = space();
      p1 = element("p");
      t5 = text("age is ");
      t6 = text(/*age*/ ctx[2]);
      t7 = space();
      div0 = element("div");
      button0 = element("button");
      button0.textContent = "change name";
      t9 = space();
      button1 = element("button");
      button1.textContent = "change age";
    },
    l: function claim(nodes) {
      // ...
    },
    // 將create生命周期生成的dom節點掛載到target上面去
    m: function mount(target, anchor) {
      insert_dev(target, div1, anchor);
      append_dev(div1, p0);
      append_dev(p0, t0);
      append_dev(p0, t1);
      append_dev(p0, t2);
      append_dev(p0, t3);
      append_dev(div1, t4);
      append_dev(div1, p1);
      append_dev(p1, t5);
      append_dev(p1, t6);
      append_dev(div1, t7);
      append_dev(div1, div0);
      append_dev(div0, button0);
      append_dev(div0, t9);
      append_dev(div0, button1);

      if (!mounted) {
        dispose = [
          // 添加click事件監聽
          listen_dev(button0, "click", /*handleChangeName*/ ctx[3], false, false, false),
          listen_dev(button1, "click", /*handleChangeAge*/ ctx[4], false, false, false)
        ];

        mounted = true;
      }
    },
    // 修改變數makedirty後,下一次微任務時會調用update方法
    p: function update(ctx, [dirty]) {
      if (dirty & /*firstName*/ 1) set_data_dev(t1, /*firstName*/ ctx[0]);
      if (dirty & /*lastName*/ 2) set_data_dev(t3, /*lastName*/ ctx[1]);
      if (dirty & /*age*/ 4) set_data_dev(t6, /*age*/ ctx[2]);
    },
    i: noop,
    o: noop,
    d: function destroy(detaching) {
      // ...
            mounted = false;
      // 移除事件監聽
      run_all(dispose);
    }
  };

  return block;
}

再來看看update方法裡面的 if (dirty & /*firstName*/ 1) set_data_dev(t1, /*firstName*/ ctx[0]);

當firstName的值被修改時,firstName是第一個定義的變數,i=0。按照上面的二進位計算component.$$.dirty[(i / 31) | 0] |= (1 << (i % 31));,此時dirty[0]= 0 |(1<<0)=01
if (dirty & /*firstName*/ 1) set_data_dev(t1, /*firstName*/ ctx[0]);就變成了if (01 & /*firstName*/ 1) set_data_dev(t1, /*firstName*/ ctx[0]);。此時if條件滿足,執行set_data_dev(t1, /*firstName*/ ctx[0]);。這裡的t1就是t1 = text(/*firstName*/ ctx[0]);,使用firstName變數的dom。

同理當lastName的值被修改時,lastName是第二個定義的變數,i=1。按照上面的二進位計算component.$$.dirty[(i / 31) | 0] |= (1 << (i % 31));,此時dirty[0]= 0 |(1<<1)=10
if (dirty & /*lastName*/ 2) set_data_dev(t3, /*lastName*/ ctx[1]);就變成了if (10 & /*lastName*/ 2) set_data_dev(t3, /*lastName*/ ctx[1]);。此時if條件滿足,執行set_data_dev(t3, /*lastName*/ ctx[1]);。這裡的t3就是t3 = text(/*lastName*/ ctx[1]);,使用lastName變數的dom。

set_data_dev方法

	  function set_data_dev(text2, data) {
	    data = "" + data;
	    if (text2.wholeText === data)
	      return;
	    text2.data = data;
	  }

這個方法很簡單,判斷dom裡面的值和新的值是否相等,如果不等直接修改dom的data屬性,將最新值更新到dom裡面去。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在Winform系統開發中,為了對系統的工具欄/菜單進行動態的控制,我們對系統的工具欄/菜單進行動態配置,這樣可以把系統的功能彈性發揮到極致。通過動態工具欄/菜單的配置方式,我們可以很容易的為系統新增所需的功能,通過許可權分配的方式,可以更有效的管理系統的菜單分配到不同的角色用戶,也就是插件化的處理方... ...
  • 大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家分享的是i.MXRT1170內部RAM的ECC初始化工作可全部由ROM完成。 痞子衡之前寫了三篇文章 《M7 FlexRAM ECC》、《M4 L-MEM ECC》、《MECC64》 分別介紹了 i.MXRT1170 片上 2MB RAM 的不 ...
  • 一.安裝anaconda3 前往清華園鏡像下載anaconda3的安裝包 https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/?C=M&O=D 選擇最新鏡像Anaconda3-2023.09-0-Linux-x86_64.sh 安裝 1 bas ...
  • 大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家分享的是i.MXRT1170 MECC64功能特點及其保護片內OCRAM1,2之道。 ECC是 “Error Correcting Code” 的簡寫,ECC 能夠實現錯誤檢查和糾正,含有 ECC 功能的記憶體一般稱為 ECC 記憶體,使用了 EC ...
  • 本文在前一篇文章的基礎上,對進程執行監控工具(execsnoop)進行了升級,實時列印進程執行時傳入的參數列表;並通過 `kprobe` 和 `tracepoint` 兩種方式,綁定 eBPF 程式,給出了代碼實現。同時,對這兩種 eBPF 事件類型進行了簡單比較。顯然,在你手動開發一個 eBPF ... ...
  • jflashlite的燒寫速度要比mdk的燒寫速度快很多。雖然兩者底層都調用了jlink.exe,但是燒寫流程有區別。 MDK比較保守,不管怎麼樣先擦除扇區然後再執行寫入。 jflashlite比較靈活,先進行扇區校驗,如果不一致,才執行擦除和寫入。這會大大提升微小修改的程式燒寫時間。 當然你可以用 ...
  • Partial Update 數據打寬 通過不同的流寫不同的欄位,打寬了數據的維度,填充了數據內容;如下所示: --FlinkSQL參數設置 set `table.dynamic-table-options.enabled` = `true`; SET `env.state.backend` = ` ...
  • 實例項目使用 vite5 + vue3 + ts,項目地址 vite-vue3-charts,預覽地址 https://weizwz.com/vite-vue3-charts 準備工作 1. 註冊為百度地圖開發者 官網地址,然後在 應用管理 -> 我的應用 里,創建應用,創建好後複製 AK 2. 在 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...