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 ...
随机推荐
- FPGA常用IP核
前言: 芯片行业中的IP,一般称为IP(Intellectual Property)核,是具有知识产权核的集成电路芯核的总称.说白了就是厂家实现的具有特定功能工具,然后我们可以直接调用,就相当于是函数 ...
- Spring Cloud 整合
前言 玩SpringCloud之前最好懂SpringBoot,别搞撑死骆驼的事.Servlet整一下变成Spring:SSM封装.加入东西就变为SpringBoot:SpringBoot再封装.加入东 ...
- Kafka 如何保证消息消费的全局顺序性
哈喽大家好,我是咸鱼 今天我们继续来讲一讲 Kafka 当有消息被生产出来的时候,如果没有指定分区或者指定 key ,那么消费会按照[轮询]的方式均匀地分配到所有可用分区中,但不一定按照分区顺序来分配 ...
- Alist手动安装并使用教程
一.官方文档及下载地址 1.官方文档 AList文档 2.下载地址 alist · GitHub 二.下载并解压文件 以Windows为例,下载指定版本的文件. 三.运行 1.解压文件并进入文件夹: ...
- HTML5语法总结大全
参考书籍: <HTML与CSS3基础教程> 参考视频: HTML5完整教学通俗易懂 2023新版前端Web开发HTML5+CSS3+移动web视频教程,前端web入门首选黑马程序员 参考网 ...
- 0x02.加密和编码
识别算法编码类型 看密文位数 看密文特征(数字.字母.大小写.符号等) 看当前密文存在的地方(web.数据库.操作系统等) 密码存储加密 md5:16位和32位由0-9和a-f组成的字符串 ,该加密方 ...
- SpringCore完整学习教程7,入门级别
本章可以说是完结,下一章可能讲kotlin+springboot 本章从第九章开始: 9. Creating Your Own Auto-configuration 如果您在开发共享库的公司工作,或者 ...
- HBase应用方案
HBase性能优化方法:
- 笔记3:Tensorflow2.0实战之MNSIT数据集
最近Tensorflow相继推出了alpha和beta两个版本,这两个都属于tensorflow2.0版本:早听说新版做了很大的革新,今天就来用一下看看 这里还是使用MNSIT数据集进行测试 导入必要 ...
- SpringBoot整合Filter过滤器
话不多说,直接上核心代码 1.先创建一个Filter类 package com.qbb.reggie.filter; import com.alibaba.fastjson.JSON; import ...