资讯专栏INFORMATION COLUMN

vue - 响应式原理梳理(二)

mochixuan / 3051人阅读

摘要:原型方法通过原型方法方法来挂载实例。当响应式属性发生变化时,会通知依赖列表中的对象进行更新。此时,对象执行方法,重新渲染节点。在执行过程中,如果需要读取响应式属性,则会触发响应式属性的。总结响应式属性的原理

vue实例 初始化 完成以后,接下来就要进行 挂载

vue实例挂载,即为将vue实例对应的 template模板,渲染成 Dom节点

原型方法 - $mount

  通过原型方法 $mount方法 来挂载vue实例。

  挂载vue实例时,经历一下几个重要步骤:

生成render函数;

生成vue实例的监听器watcher;

执行render函数,将vue实例的template模板转化为VNode节点树;

执行update函数,将VNode节点树转化为dom节点树;

    // 挂载Vue实例
    Vue$3.prototype.$mount = function(el, hydrating) {
        // el为dom元素对应的选择器表达式,根据选择器表达式,获取dom元素
        el = el && query(el);

        ...
        
        // this->Vue实例,或者是组件实例对象
        var options = this.$options;
        
        // 解析模板,将模板转换为render渲染函数
        if(!options.render) {
            // 一般是组件实例的构造函数的options中会直接有template这个属性
            var template = options.template;
            if(template) {
                if(typeof template === "string") {
                    if(template.charAt(0) === "#") {
                        template = idToTemplate(template);
                        /* istanbul ignore if */
                        if("development" !== "production" && !template) {
                            warn(
                                ("Template element not found or is empty: " + (options.template)),
                                this
                            );
                        }
                    }
                } else if(template.nodeType) {
                    template = template.innerHTML;
                } else {
                    {
                        warn("invalid template option:" + template, this);
                    }
                    return this
                }
            } else if(el) {
                // 获取dom节点的outerHTML
                template = getOuterHTML(el);
            }
            // template,模板字符串
            if(template) {
                /* istanbul ignore if */
                if("development" !== "production" && config.performance && mark) {
                    mark("compile");
                }
                
                // 将html模板字符串编译为渲染函数
                var ref = compileToFunctions(template, {
                    // 换行符
                    shouldDecodeNewlines : shouldDecodeNewlines,
                    // 分割符
                    delimiters : options.delimiters,
                    // 注释
                    comments : options.comments
                }, this);
                // 获取渲染函数
                var render = ref.render;
                // 获取静态渲染函数
                var staticRenderFns = ref.staticRenderFns;
                // 将渲染函数添加到Vue实例对象的配置项options中
                options.render = render;
                // 将静态渲染函数添加到Vue实例对象的配置项options中
                options.staticRenderFns = staticRenderFns;

                /* istanbul ignore if */
                if("development" !== "production" && config.performance && mark) {
                    mark("compile end");
                    measure(((this._name) + " compile"), "compile", "compile end");
                }
            }
        }
        return mountComponent.call(this, el, hydrating)
    };
    
    
    // 挂载vue实例
    function mountComponent(vm, el, hydrating) {
        vm.$el = el;
        
        ...
        
        updateComponent = function() {
            vm._update(vm._render(), hydrating);
        };

        // 给Vue实例或者是组件实例创建一个监听器, 监听updateComponent方法
        vm._watcher = new Watcher(vm, updateComponent, noop);
        
        ...
        
        return vm;
    }

watcher 对象在构建过程中,会作为观察者模式中的 Observer,会被添加到 响应式属性的dep对象的依赖列表 中。

  当响应式属性发生变化时,会通知依赖列表中的watcher对象进行更新。

  此时,watcher 对象执行 updateComponent 方法,重新渲染 dom节点

watcher

  在 挂载vue实例 时, 会为 vue实例 构建一个 监听者watcher

 // vm => 当前监听者对应的vue实例
 // expOfFn => 需要监听的表达式
 // cb => 监听回调方法
 // options => 选项,比如deep、immediate
 // vm.$watch("message", function(newValue) {...})
 
 var Watcher = function Watcher(vm, expOrFn, cb,  options) {
        this.vm = vm;

        vm._watchers.push(this);
        
        ...
        
        // 监听器订阅的Dep对象实例
        this.deps = [];
        this.newDeps = [];
        // 存储监听器已经订阅的Dep对象实例的id,防止重复订阅。
        // 每一个Dep对象实例都有一个ID
        this.depIds = new _Set();
        this.newDepIds = new _Set();
        
        // 监听器监听的表达式
        this.expression = expOrFn.toString();
        // 初始化getter,getter用于收集依赖关系
        if(typeof expOrFn === "function") {
            // expOfFn 为 函数
            // 对应:给vue实例建立watcher
            this.getter = expOrFn;
        } else {
            // epOfFn 为 字符串
            // 对应:在vue实例中建立监听
            // 比如 vm.$watch("message", function() {...})
            this.getter = parsePath(expOrFn);
        }

        this.value = this.lazy
            ? undefined
            : this.get();
    };
    
    // 执行watcher的getter方法,用于收集响应式属性dep对象 和 watcher的依赖关系
    // 在vue实例挂载过程中, getter = updateComponent
    Watcher.prototype.get = function get() {
        // 将Dep.target设置为当前Watcher对象实例
        pushTarget(this);
        var value;
        var vm = this.vm;
        
        ...
        
        value = this.getter.call(vm, vm);
        
        ...
        
        return value
    };
    
    // watcher 更新
    Watcher.prototype.update = function update() {
        ...
        
        this.get()
        
        ...
    };
    
    // 将watcher添加到dep属性的依赖列表中
    Watcher.prototype.addDep = function addDep(dep) {
        var id = dep.id;
        if(!this.newDepIds.has(id)) {
            this.newDepIds.add(id);
            this.newDeps.push(dep);
            if(!this.depIds.has(id)) {
                dep.addSub(this);
            }
        }
    };
    

  在挂载vue实例时,watcher对象会在构建过程中会执行 updateComponent 方法。

  执行 updateComponent 方法分为两个过程:

执行 render 方法,返回 VNode节点树

执行 update 方法,将 VNode节点树 渲染为 dom节点树

  执行 render 方法时,会触发响应式属性的 getter 方法,将 watcher 添加到 dep对象的依赖列表中

render

render 方法是 vue实例 在挂载时由 template 编译成的一个 渲染函数

tempalte:
    
{{message}}
render: // 执行render, 需要读取响应式属性message,触发message的getter方法 (function anonymous() { with(this){return _c("div",{attrs:{"id":"app"}},[_v(_s(message))])} }) // _s, 将this.message转化为字符串 // _v, 生成文本节点对应的VNode // _c, 生成"div"元素节点对应的Vnode

render 函数返回 VNode节点 , 用于 渲染Dom节点

  在执行过程中,如果需要读取 响应式属性,则会触发 响应式属性getter

  在 getter 方法中, 将 watcher 对象添加到 响应式属性dep对象的依赖列表 中。

修改响应式属性

修改 响应式属性时,会触发响应式属性的 setter 方法。此时,响应式属性的 dep对象执行 notify 方法,遍历自己的 依赖列表subs, 逐个通知subs中的 watcher 去更新。

watcher 对象执行 update 方法,重新调用 render 方法生成 Vnode节点树,然后 对比新旧Vnode节点树 的不同,更新dom树

总结

响应式属性 的原理:

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/101402.html

相关文章

  • vue - 响应原理梳理(一)

    摘要:问题为什么修改即可触发更新和的关联关系官方介绍的官网文档,对响应式属性的原理有一个介绍。因此本文在源码层面,对响应式原理进行梳理,对关键步骤进行解析。 描述  我们通过一个简单的 Vue应用 来演示 Vue的响应式属性: html: {{message}} js: let vm = new Vue({ el: #ap...

    weknow619 评论0 收藏0
  • vue总结系列--数据驱动和响应

    摘要:由于是需要兼容的后台系统,该项目并不能使用到等技术,因此我在上的经验大都是使用原生的编写的,可以看见一个组件分为两部分视图部分,和数据部分。 在公司里帮项目组里开发后台系统的前端项目也有一段时间了。 vue这种数据驱动,组件化的框架和react很像,从一开始的快速上手基本的开发,到后来开始自定义组件,对element UI的组件二次封装以满足项目需求,期间也是踩了不少坑。由于将来很长一...

    AbnerMing 评论0 收藏0
  • JS核心知识点梳理——原型、继承(下)

    摘要:引言上篇文章介绍原型,这篇文章接着讲继承,呕心沥血之作,大哥们点个赞呀明确一点并不是真正的面向对象语言,没有真正的类,所以我们也没有类继承实现继承有且仅有两种方式,和原型链在介绍继承前我们先介绍下其他概念函数的三种角色一个函数,有三种角色。 showImg(https://segmentfault.com/img/bVbo4hv?w=1800&h=1000); 引言 上篇文章介绍原型,...

    joyqi 评论0 收藏0
  • JavaScript - 收藏集 - 掘金

    摘要:插件开发前端掘金作者原文地址译者插件是为应用添加全局功能的一种强大而且简单的方式。提供了与使用掌控异步前端掘金教你使用在行代码内优雅的实现文件分片断点续传。 Vue.js 插件开发 - 前端 - 掘金作者:Joshua Bemenderfer原文地址: creating-custom-plugins译者:jeneser Vue.js插件是为应用添加全局功能的一种强大而且简单的方式。插....

    izhuhaodev 评论0 收藏0
  • 浅谈Vue中计算属性computed的实现原理

    摘要:虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。当某个属性发生变化,触发拦截函数,然后调用自身消息订阅器的方法,遍历当前中保存着所有订阅者的数组,并逐个调用的方法,完成响应更新。 虽然目前的技术栈已由Vue转到了React,但从之前使用Vue开发的多个项目实际经历来看还是非常愉悦的,Vue文档清晰规范,api设计简洁高效,对前端开发人员友好,上手快,甚至个人认为在很多...

    laznrbfe 评论0 收藏0

发表评论

0条评论

mochixuan

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<