svelte文件编译为js后的结构

源代码:

      let firstName = '张'    let lastName = '三'    let age = 18    function handleChangeName() {      firstName = '王'      lastName = '二'    }    function handleChangeAge() {      age = 28    }    

fullName is {firstName} {lastName}

age is {age}

编译后的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 < 100  // 有供应商商权限  supplier = 1 < 010  // 有运营权限  admin =    1 < 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< 01。
  • i=1,计算结果为1 < 10。
  • i=32,计算结果为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里面去。