🚀Svelte原理和进阶看这篇就够了🚀2

简介: 🚀Svelte原理和进阶看这篇就够了🚀

Svelte运行时原理

现在我们又有了一个新的问题。我们已经可以感知到值的变化,那是怎么将值得变化更新到页面中的了。

你可能马上想到的是create_fragment返回的updata方法啊。这里仅仅是提供了更新页面DOM的方法,那是什么样的时机调用这个更新方法的呢?

init方法

其实,svelte的编译结果是运行时运行的代码。在进入运行时,首先执行init方法,该方法大致流程如下:

  • 💎初始化状态
  • 💎初始化周期函数
  • 💎执行instance方法,在回调函数中标记脏组件
  • 💎执行所有beforeUpdate生命周期的函数
  • 💎执行创建片段create_fragment函数
  • 💎挂载当前组件并执行create_fragement返回的m(mounted)方法
  • 💎执行flush方法

你可以跳过这段代码,不影响阅读

export function init(
  component,
  options,
  instance,
  create_fragment,
  not_equal,
  props,
  append_styles,
  dirty = [-1]
) {
  const parent_component = current_component;
  set_current_component(component);
  const $$: T$$ = component.$$ = {
    fragment: null,
    ctx: [],
    // state
    props,
    update: noop,
    not_equal,
    bound: blank_object(),
    // lifecycle
    on_mount: [],
    on_destroy: [],
    on_disconnect: [],
    before_update: [],
    after_update: [],
    context: new Map(options.context || (parent_component ? parent_component.$$.context : [])),
    // everything else
    callbacks: blank_object(),
    dirty,
    skip_bound: false,
    root: options.target || parent_component.$$.root
  };
  append_styles && append_styles($$.root);
  let ready = false;
  $$.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)) {
            if (!$$.skip_bound && $$.bound[i]) $$.bound[i](value);
            if (ready) make_dirty(component, i);
        }
        return ret;
    })
    : [];
  $$.update();
  ready = true;
  run_all($$.before_update);
  // `false` as a special case of no DOM component
  $$.fragment = create_fragment ? create_fragment($$.ctx) : false;
  if (options.target) {
    if (options.hydrate) {
        start_hydrating();
        const nodes = children(options.target);
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        $$.fragment && $$.fragment!.l(nodes);
        nodes.forEach(detach);
    } else {
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        $$.fragment && $$.fragment!.c();
    }
    if (options.intro) transition_in(component.$$.fragment);
    mount_component(component, options.target, options.anchor, options.customElement);
    end_hydrating();
    flush();
  }
  set_current_component(parent_component);
}

看起来,flush方法很可能才是我们需要的答案。

flush方法

flush的方法主要做了一件事:

遍历需要更新的组件(dirty_components),然后更新它,并且调用afterUpdate方法。

export function flush() {
  // Do not reenter flush while dirty components are updated, as this can
  // result in an infinite loop. Instead, let the inner flush handle it.
  // Reentrancy is ok afterwards for bindings etc.
  if (flushidx !== 0) {
    return;
  }
  const saved_component = current_component;
  do {
    // first, call beforeUpdate functions
    // and update components
    try {
        while (flushidx < dirty_components.length) {
            const component = dirty_components[flushidx];
            flushidx++;
            set_current_component(component);
            update(component.$$);
        }
    } catch (e) {
        // reset dirty state to not end up in a deadlocked state and then rethrow
        dirty_components.length = 0;
        flushidx = 0;
        throw e;
    }
    set_current_component(null);
    dirty_components.length = 0;
    flushidx = 0;
    // then, once components are updated, call
    // afterUpdate functions. This may cause
    // subsequent updates...
    for (let i = 0; i < render_callbacks.length; i += 1) {
        const callback = render_callbacks[i];
        if (!seen_callbacks.has(callback)) {
            // ...so guard against infinite loops
            seen_callbacks.add(callback);
            callback();
        }
    }
    render_callbacks.length = 0;
  } while (dirty_components.length);
  while (flush_callbacks.length) {
    flush_callbacks.pop()();
  }
  update_scheduled = false;
  seen_callbacks.clear();
  set_current_component(saved_component);
}

我们再来看看具体的更新操作update函数做了啥

  • 首先执行所有的before_update方法
  • 然后执行create_fragment返回的p(update)方法
function update($$) {
    if ($$.fragment !== null) {
        $$.update();
        run_all($$.before_update);
        const dirty = $$.dirty;
        $$.dirty = [-1];
        $$.fragment && $$.fragment.p($$.ctx, dirty);
        $$.after_update.forEach(add_render_callback);
    }
}

好了,我们总结下:在运行时

  • 💎首先,初始化状态、初始化周期函数
  • 💎接着,执行instance方法,在回调函数中标记脏组件
  • 💎接着,执行所有beforeUpdate生命周期的函数
  • 💎然后,执行创建片段create_fragment函数
  • 💎接着,挂载当前组件并执行create_fragement返回的m(mounted)方法
  • 💎然后,执行flush方法
  • 💎首先,执行所有的before_update方法
  • 💎然后,执行create_fragment返回的p(update)方法
  • 💎最后,执行afterUpdate方法

总结

好了,今天的分享就这些了,总的来说,Svelte的响应式原理虽然很朴素,但是却拥有了更好的性能,同时也降低了开发者的记忆负担。我觉得这是svelte最成功的地方。

如果你发现文章有错误的地方,请及时告诉我,十分感谢。

今天的分享就到这了,如果你觉得还不错,也可以关注我的微信公众号:萌萌哒草头将军

相关文章
|
JavaScript 前端开发 安全
抽象语法树(AST):理解JavaScript代码的抽象语法树
抽象语法树(AST):理解JavaScript代码的抽象语法树
|
存储 前端开发 JavaScript
【面试题】面试官问:如果有100个请求,你如何使用Promise控制并发?
【面试题】面试官问:如果有100个请求,你如何使用Promise控制并发?
288 0
|
网络协议 安全
|
11月前
|
搜索推荐 安全 API
|
Linux 网络安全 API
云计算|OpenStack|社区版OpenStack安装部署文档(五 --- 计算服务nova安装部署---Rocky版)
云计算|OpenStack|社区版OpenStack安装部署文档(五 --- 计算服务nova安装部署---Rocky版)
718 0
|
缓存 JavaScript 算法
🚀Svelte原理和进阶看这篇就够了🚀1
🚀Svelte原理和进阶看这篇就够了🚀
|
jenkins Linux 持续交付
Jenkins 入门与基本配置
【8月更文第31天】Jenkins 是一款开源的持续集成/持续交付(CI/CD)工具,广泛应用于自动化软件的构建、测试和部署流程。对于想要提高开发效率和软件质量的团队而言,Jenkins 提供了一个易于使用的解决方案。本文将详细介绍 Jenkins 的安装过程、初始设置以及如何创建第一个构建任务,帮助初学者快速上手 Jenkins。
1300 0
|
小程序 JavaScript
微信小程序-多个插槽-具名插槽
如何使用多个插槽 • 如果想要使用多插槽,那么就必须通过 name 属性给每个插槽起一个名称 • 如果想要使用多插槽,那么在添加组件到插槽上的时候,就需要通过 slot="插槽名称" 告诉系统,组件需要添加到哪一个插槽上 • 还有必须在插槽对应的组件的JS文件中,通过 options 中的 multipleSlots: true, 来告诉系统需要开启多插槽
403 0
|
算法 JavaScript
怎么给文件生成MD5
怎么给文件生成MD5
5244 0
|
存储 运维 Kubernetes
分布式锁实战-偶遇 etcd 后就想抛弃 Redis ?
分布式锁实战-偶遇 etcd 后就想抛弃 Redis ?
1037 0
分布式锁实战-偶遇 etcd 后就想抛弃 Redis ?