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

svelte响应式原理的更多相关文章

  1. Vue.js学习 Item12 – 内部响应式原理探究

    深入响应式原理 大部分的基础内容我们已经讲到了,现在讲点底层内容.Vue.js 最显著的一个功能是响应系统 —— 模型只是普通对象,修改它则更新视图.这让状态管理非常简单且直观,不过理解它的原理也很重 ...

  2. Vue.js响应式原理

      写在前面 因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出. 文章的原地址:answershuto/learnV ...

  3. vue.js响应式原理解析与实现

    vue.js响应式原理解析与实现 从很久之前就已经接触过了angularjs了,当时就已经了解到,angularjs是通过脏检查来实现数据监测以及页面更新渲染.之后,再接触了vue.js,当时也一度很 ...

  4. 深入浅出Vue基于“依赖收集”的响应式原理(转)

    add by zhj: 文章写的很通俗易懂,明白了Object.defineProperty的用法 原文:https://zhuanlan.zhihu.com/p/29318017 每当问到VueJS ...

  5. vue深入响应式原理

    vue深入响应式原理 深入响应式原理 — Vue.jshttps://cn.vuejs.org/v2/guide/reactivity.html 注意:这里说的响应式不是bootsharp那种前端UI ...

  6. 深度解析 Vue 响应式原理

    深度解析 Vue 响应式原理 该文章内容节选自团队的开源项目 InterviewMap.项目目前内容包含了 JS.网络.浏览器相关.性能优化.安全.框架.Git.数据结构.算法等内容,无论是基础还是进 ...

  7. Vue 数据响应式原理

    Vue 数据响应式原理 Vue.js 的核心包括一套“响应式系统”.“响应式”,是指当数据改变后,Vue 会通知到使用该数据的代码.例如,视图渲染中使用了数据,数据改变后,视图也会自动更新. 举个简单 ...

  8. [vuejs] 深入响应式原理

    深入响应式原理 现在是时候深入一下了!Vue 最独特的特性之一,是其非侵入性的响应式系统.数据模型仅仅是普通的 JavaScript 对象.而当你修改它们时,视图会进行更新.这使得状态管理非常简单直接 ...

  9. vue 数组 新增元素 响应式原理 7种方法

    1.问题 思考一个问题,以下代码: <!DOCTYPE html> <html> <head> <meta charset="utf-8" ...

  10. vue 数据劫持 响应式原理 Observer Dep Watcher

    1.vue响应式原理流程图概览 2.具体流程 (1)vue示例初始化(源码位于instance/index.js) import { initMixin } from './init' import ...

随机推荐

  1. Keepalived+Nginx高可用案例(抢占式与非抢占式)

    (1)下载安装Keepalived源码包 Keepalived官网源码包下载地址 在服务器上解压 tar -xf keepalived-2.2.8.tar.gz 安装相关前置依赖 yum -y ins ...

  2. 文心一言 VS 讯飞星火 VS chatgpt (143)-- 算法导论12.1 3题

    三.用go语言,设计一个执行中序遍历的非递归算法.(提示:一种容易的方法是使用栈作为辅助数据结构;另一种较复杂但比较简洁的做法是不使用栈,但要假设能测试两个指针是否相等.) 文心一言,代码正常运行: ...

  3. 使用QPainter制作一个简易的相册

    PlayImage 记得一键三连哦 一个使用简单的QPainter绘图事件实现图片播放器的简易demo 支持图片切换 支持多路更新,自己扩展即可 支持幻灯片播放 PlayImage自定义控件支持复用, ...

  4. json数组根据某属性去重

    数据: let arry = [ {name: "张三", age: 23, work: '计算机'}, {name: "王五", age: 29, work: ...

  5. 09 - Shell流程控制语句

    1. if-else语句 能够使用if条件语句进行条件判断 1.1 if 语法 if 条件 then 命令 fi if 条件; then 命令; fi 1.2 if-else 语法 if 条件 the ...

  6. [ABC266Ex] Snuke Panic (2D)

    Problem Statement Takahashi is trying to catch many Snuke. There are some pits in a two-dimensional ...

  7. springBoot——多环境开发

    不常用的application.properties版的 常用的:application.yml版 #多环境开发,设置启用环境 spring: profiles: active: test --- # ...

  8. CentOS 8.1成功安装最新Node.js 20教程(含用到的全部命令和截图演示)

    yum换镜像和源 CentOS 已经停止维护的问题.2020 年 12 月 8 号,CentOS 官方宣布了停止维护 CentOS Linux 的计划,并推出了 CentOS Stream 项目,Ce ...

  9. 12 HTTP的实体数据

    目录 数据类型和编码 HTTP协议为什么要关心 body MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型 HTTP 常用数据类型 MIM ...

  10. 好家伙,这个开源项目硬生生复制了一个 ChatGPT Plus 出来

    最近有一款聊天机器人框架 Lobe Chat 火出了天际,它不仅支持多模态,支持语音会话,还有一个强大的 Function Calling 插件生态系统(可以作为 ChatGPT 插件的平替).最重要 ...