资讯专栏INFORMATION COLUMN

vue 源码自问自答-响应式原理

ityouknow / 675人阅读

摘要:源码自问自答响应式原理最近看了源码和源码分析类的文章感觉明白了很多,但是仔细想想却说不出个所以然。会在对象的这个被获取时触发,会在这个对象的被修改时触发。在初始化时,将对象上的所有,都包装成拥有和的属性。

vue 源码自问自答-响应式原理

最近看了 Vue 源码和源码分析类的文章,感觉明白了很多,但是仔细想想却说不出个所以然。

所以打算把自己掌握的知识,试着组织成自己的语言表达出来

不打算平铺直叙的写清楚 vue 源码的前因后果和全部细节,而是以自问自答的形式,回答我自己之前的疑惑,

如果有错误的地方,欢迎指正哈~

Vue 的双向数据绑定原理

Vue 实现响应式的核心 API 是 ES5 的 Object.defineProperty(obj,key,descriptor),Vue 的「响应式」和「依赖收集」都依靠这个 API

它接受 3 个参数,分别是 obj / key / 描述符,返回的是一个包装后的对象

它的作用就是,用这个 API 包装过后的对象可以拥有 getter 和 setter 函数。

getter 会在对象的这个 key 被获取时触发,setter 会在这个对象的 key 被修改时触发。

一个 Vue 项目的开始, 通常是从 Vue 构造函数的实例化开始的。

new Vue()的时候会执行一个_init()方法,会初始化属性,比如 props/event/生命周期钩子,也包括 data 对象的初始化。

Vue 在初始化时,将 data 对象上的所有 key,都包装成拥有 getter 和 setter 的属性。

渲染页面时,会执行 render function(无论是用 template 还是 render 最终都会生成 render 函数)

执行 render function 会获取 data 对象上的属性,所以会触发对应属性的 getter 函数

getter 触发时,主要就做 2 个事情。 1.把值返回 2.依赖收集,也就是讲 watcher 存放到 Dep 实例的一个队列里。

当修改对象的值触发 setter,setter 同样是做 2 个事情。1.把 newVal 设置好 2.用一个循环通知之前存放在 dep 实例中 watcher 对象们,watcher 对象调用各自的 update 方法来更新视图

实现双向数据绑定的demo1 - 忽略「收集依赖」的版本
function cb() {
    console.log("更新视图");
}

function defineReactve(obj, key, val) {
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: () => {
            console.log("触发了getter");
            return val;
        },
        set: newVal => {
            console.log("触发了setter");
            if (newVal === val) return;
            val = newVal;
            cb()
        }
    });
}

function observe(data) {
    function walk(data) {
        Object.keys(data).forEach(key => {
            if (typeof data[key] === "object") {
                walk(data[key]);
            } else {
                defineReactve(data, key, data[key]);
            }
        });
    }
    walk(data);
}

class Vue {
    constructor(options) {
        this._data = options.data;
        observe(this._data);
    }
}

var vm = new Vue({
    data: {
        msg: "test",
        person: {
            name: "ziwei",
            age: 18
        }
    }
});

vm._data.person.name = "hello"

// 触发setter和cb函数,从而视图更新
实现双向数据绑定的demo2 - 「收集依赖」的版本
        function defineReactve( obj, key, val ) {
            const dep = new Dep()
            Object.defineProperty( obj, key, {
                enumerable: true,
                configurable: true,
                get: () => {
                    console.log( "触发了getter" );
                    dep.addSub(Dep.target)
                    return val;
                },
                set: newVal => {
                    console.log( "触发了setter" );
                    if ( newVal === val ) return;
                    val = newVal;
                    dep.notify()  // 通知队列的wather去update视图
                }
            } );
        }

        function observe( data ) {
            function walk( data ) {
                Object.keys( data ).forEach( key => {
                    if ( typeof data[ key ] === "object" ) {
                        walk( data[ key ] );
                    } else {
                        defineReactve( data, key, data[ key ] );
                    }
                } );
            }
            walk( data );
        }

        class Dep{
            constructor(){
                this.subs = []
            }

            addSub(){
                this.subs.push(Dep.target)
            }

            notify(){
                this.subs.forEach(sub => {
                    sub.update()
                })
            }
        }

        Dep.target = null

        class Watcher{
            constructor(){
                Dep.target = this
            }

            update(){
                console.log("update更新视图啦~")
            }
        }

        class Vue {
            constructor( options ) {
                this._data = options.data;
                observe( this._data );

                new Watcher()           // 模拟页面渲染,触发getter,依赖收集的效果
                this._data.person.name
            }
        }

        var vm = new Vue( {
            data: {
                msg: "test",
                person: {
                    name: "ziwei",
                    age: 18
                }
            }
        } );

        vm._data.person.name = "hello"
一些省略掉的环节

这样就是 Vue 响应式的一个基本原理,不过我描述的过程中,也省略了很多环节,比如

Vue 是如何实现给 data 对象上的属性都拥有 getter 和 setter 的

为什么要进行「依赖收集」,

如何避免重复「收集依赖」

watcher 调用 update,也并不是直接更新视图。实现上中间还有 patch 的过程以及使用队列来异步更新的策略。

Vue 是如何实现给 data 对象上的属性都拥有 getter 和 setter 的
通过循环data对象,给对象的每一个key,用Object.defineProperty包装

遍历时,如果发现data[key]也是对象的话,需要用递归
为什么要进行「依赖收集」?

举2个场景的栗子

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

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

相关文章

  • Java经典

    摘要:请注意,我们在聊聊单元测试遇到问题多思考多查阅多验证,方能有所得,再勤快点乐于分享,才能写出好文章。单元测试是指对软件中的最小可测试单元进行检查和验证。 JAVA容器-自问自答学HashMap 这次我和大家一起学习HashMap,HashMap我们在工作中经常会使用,而且面试中也很频繁会问到,因为它里面蕴含着很多知识点,可以很好的考察个人基础。但一个这么重要的东西,我为什么没有在一开始...

    xcold 评论0 收藏0
  • 利用express+socket.io实现一个简易版聊天室

    摘要:用伪代码来模拟下长轮询的过程前端利用下面函数进行请求后端代码做如下更改利用随机数的大小来模拟是否有新数据有新数据来了长轮询的确减少了请求的次数,但是它也有着很大的问题,那就是耗费服务器的资源。 写在前面 最近由于利用node重构某个项目,项目中有一个实时聊天的功能,于是就研究了一下聊天室,在线demo|源码,欢迎大家反馈。这个聊天室的主要利用到了socket.io和express。这个...

    Chaz 评论0 收藏0

发表评论

0条评论

ityouknow

|高级讲师

TA的文章

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