首页 > 其他分享 >svelte响应式原理

svelte响应式原理

时间:2023-12-28 20:24:54浏览次数:28  
标签:firstName lastName ctx dev 响应 let dirty 原理 svelte

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里面去。

标签:firstName,lastName,ctx,dev,响应,let,dirty,原理,svelte
From: https://www.cnblogs.com/heavenYJJ/p/17933469.html

相关文章

  • 《FPGA原理和结构》——读书笔记
    最近做了一个关于FPGA的项目后,读了《FPGA原理和结构》这本书。主要梗概内容和想法如下。第一章:理解FPGA所需要的基础知识理解FPGA我们需要数电的组合逻辑、时序逻辑等内容的知识。FPGA(20世纪70年度发展起来的,因为其具有通过组合使用器件内大量的逻辑块来实现所需的电路,比以往侠......
  • Diffie-Hellman Key Agreement Protocol 安全漏洞 (CVE-2002-20001)【原理扫描】
    Diffie-HellmanKeyAgreementProtocol是一种密钥协商协议。它最初在Diffie和Hellman关于公钥密码学的开创性论文中有所描述。该密钥协商协议允许Alice和Bob交换公钥值,并根据这些值和他们自己对应的私钥的知识,安全地计算共享密钥K,从而实现进一步的安全通信。仅知道交换......
  • Web自动化测试原理
    Web自动化测试原理:通过控制浏览器进行一系列的自动化操作浏览器驱动:用来控制浏览器:是浏览器厂商开发提供一系列的HTTP的接口脚本--->HTTP接口--->浏览器驱动----内部API--->浏览器为什么使用Selenium?通过面向对象的方式,封装了这些内部接口,方便调用Selenium主要有两大对......
  • Socket和Http的通讯原理,遇到攻击会受到哪些影响以及如何解决攻击问题。
    Socket通信原理:Socket是一种应用程序编程接口(API),用于在单个进程或多个进程之间进行通信。它提供了一种灵活的、异步的通信方式,使应用程序可以方便地建立连接、发送数据和接收数据。Socket通信基于TCP/IP协议,它是一种面向连接、可靠的通信方式。Socket通信过程如下:a.创建Socket:创......
  • 21 mysql 一致性的底层原理
    一致性的原理:个人理解,一致性就是事务执行前后,数据在逻辑上都符合正常情况。想要保持一致性,一般有下面3种手段:第一,就是前面提到的原子性、持久性和隔离性。第二,就是数据自身带的一些参数校验,比如数据长度校验、数据类型校验。第三,就是从应用层面保持一致了。比如在银行账目系统中,保......
  • es 索引生命周期管理的原理
    es的索引生命周期管理indexlifecyclemanagement即ILM,控制着索引的创建、滚动、删除、归档,属实好用,那么它是如何实现的呢?可以想象得到,es的master执行一个定时任务,定期检查关联了ilm的索引,判断索引的状态,执行状态的流转。ILM相关代码在x-pack的plugin目录中,主类是......
  • NetCore高级系列文章04---async、await原理揭秘
    async、await本质上是C#提供的语法糖,编译器编译后是状态机的调用。先看如下的一段代码,要main方法中调用了三个await方法 将此dll进行反编译为4.0的代码如下: 可见到两个Main方法,也就是说我们在程序中Main方法上加了async关键词,编译器会编译成一个是异步的一个是非异步方法,程......
  • Docker实现原理学习
    Docker实现原理学习Namespaces命名空间(namespaces)是Linux为我们提供的用于分离进程树、网络接口、挂载点以及进程间通信等资源的方法。在日常使用Linux或者macOS时,我们并没有运行多个完全分离的服务器的需要,但是如果我们在服务器上启动了多个服务,这些服务其实会相互......
  • 基于代码一步一步教你深度学习中循环神经网络(RNN)的原理
    当谈到基于RNN(循环神经网络)的机器学习例子时,一个常见的任务是文本生成。RNN是一种能够处理序列数据的神经网络,它具有记忆能力。以下是一个基于RNN的文本生成例子,并给每一行添加了详细注释:1.importtorch2.importtorch.nnasnn3.importtorch.optimasoptim4.5.#定义......
  • 处理HTTP错误响应:Go语言中的稳健之道
    开场白:在Web开发中,HTTP错误响应是不可避免的一部分。当请求无法成功完成时,服务器会返回一个错误响应。今天,我们将深入探讨如何在Go语言中优雅地处理这些HTTP错误响应。知识点一:HTTP错误响应的常见类型HTTP错误响应通常由状态码和相应的消息组成。常见的状态码包括:404(未找到)、500(内......