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里面去。
svelte响应式原理的更多相关文章
- Vue.js学习 Item12 – 内部响应式原理探究
深入响应式原理 大部分的基础内容我们已经讲到了,现在讲点底层内容.Vue.js 最显著的一个功能是响应系统 —— 模型只是普通对象,修改它则更新视图.这让状态管理非常简单且直观,不过理解它的原理也很重 ...
- Vue.js响应式原理
写在前面 因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出. 文章的原地址:answershuto/learnV ...
- vue.js响应式原理解析与实现
vue.js响应式原理解析与实现 从很久之前就已经接触过了angularjs了,当时就已经了解到,angularjs是通过脏检查来实现数据监测以及页面更新渲染.之后,再接触了vue.js,当时也一度很 ...
- 深入浅出Vue基于“依赖收集”的响应式原理(转)
add by zhj: 文章写的很通俗易懂,明白了Object.defineProperty的用法 原文:https://zhuanlan.zhihu.com/p/29318017 每当问到VueJS ...
- vue深入响应式原理
vue深入响应式原理 深入响应式原理 — Vue.jshttps://cn.vuejs.org/v2/guide/reactivity.html 注意:这里说的响应式不是bootsharp那种前端UI ...
- 深度解析 Vue 响应式原理
深度解析 Vue 响应式原理 该文章内容节选自团队的开源项目 InterviewMap.项目目前内容包含了 JS.网络.浏览器相关.性能优化.安全.框架.Git.数据结构.算法等内容,无论是基础还是进 ...
- Vue 数据响应式原理
Vue 数据响应式原理 Vue.js 的核心包括一套“响应式系统”.“响应式”,是指当数据改变后,Vue 会通知到使用该数据的代码.例如,视图渲染中使用了数据,数据改变后,视图也会自动更新. 举个简单 ...
- [vuejs] 深入响应式原理
深入响应式原理 现在是时候深入一下了!Vue 最独特的特性之一,是其非侵入性的响应式系统.数据模型仅仅是普通的 JavaScript 对象.而当你修改它们时,视图会进行更新.这使得状态管理非常简单直接 ...
- vue 数组 新增元素 响应式原理 7种方法
1.问题 思考一个问题,以下代码: <!DOCTYPE html> <html> <head> <meta charset="utf-8" ...
- vue 数据劫持 响应式原理 Observer Dep Watcher
1.vue响应式原理流程图概览 2.具体流程 (1)vue示例初始化(源码位于instance/index.js) import { initMixin } from './init' import ...
随机推荐
- 通过滴滴技术博客:探寻造成此次P0故障的真正原因
2023年11月27日晚至2023年11月28日早晨,滴滴发生了长达12小时的P0级故障,导致滴滴核心业务都受到了影响,比如不显示定位无法打车.滴滴单车无法扫码等问题,期间滴滴进行了多次致歉 目前问题 ...
- wps表格怎么打印选中区域的内容?
打印选中区域的内容,您可以按照以下步骤进行操作: 选择要打印的区域 打开 WPS 表格,在工作表中选择您希望打印的区域.您可以拖动鼠标或使用键盘中的方向键来选择单元格. 设置打印区域 一旦您选中了需要 ...
- IDEA提示Cannot resolve symbol 'String'
一.解决方案: 1.问题原因: 系统提示Cannot resolve symbol 'String',是由于没有正确导入JDK : 2.解决方案: 在project SDK中正确配置即可. 二.完成. ...
- VideoPipe可视化视频结构化框架更新总结(2023-12-5)
项目地址:https://github.com/sherlockchou86/video_pipe_c 往期文章:https://www.cnblogs.com/xiaozhi_5638/p/1696 ...
- TCP传输层详解(计算机网络复习)
介绍:TCP/IP包含了一系列的协议,也叫TCP/IP协议族,简称TCP/IP.该协议族提供了点对点的连接机制,并将传输数据帧的封装.寻址.传输.路由以及接收方式都予以标准化 TCP/IP的分层模型 ...
- drf实战和源码剖析----学习笔记1
学自:bilibili武沛齐老师. 武老师讲课:清晰,连贯,实用,透彻,真乃名师! # 1. 什么是前后端分离 - 不分离,主要用于后台系统(CRUD)和用户量上的情况,开发起来代价小- 分离,面向用 ...
- Python 潮流周刊第一季完结(1~30)
你好,我是猫哥.庆祝 Python 潮流周刊在几天前顺利更新到了第 30 期! 我觉得这是一个很有意义的时间节点,不太短也不漫长,很适合作一个小小的总结. 我打算今后每 30 期作为一季,都给大家做一 ...
- 精致的Javascript代码
1. 统计一个数组中,每个值的个数 var cards = [1, 2, 3, 4, 3, 2, 1, 4, 5] var dict = {}; for(var i = 0; i < cards ...
- 3D 高斯喷溅 🤗 为什么图形永远不会相同
高斯喷溅 (Gaussian Splatting) 技术是一种翻天覆地的渲染手段,能够以 144 帧每秒的速度渲染出高质量的场景,这和传统的图形处理流程截然不同 这种将高斯数据转换成图像的过程,与训练 ...
- 8.解决elasticsearch深度分页问题
前面说到,分页可以使用from和size参数,类似于mysql的分页offset和limit.但是如果数据量比较大时,elasticsearch会对分页做出限制,因为此时会比较消耗性能. 为什么要限制 ...