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