Vue2是如何利用Object.defineProperty实现数据的双向绑定?

我们之前说道过Object.defineProperty方法有一关键特性,就是数据劫持,通过get/set 拦截属性的读取和修改操作。Vue主要是通过数据劫持结合发布-订阅模式来实现的,利用Object.defineProperty来劫持各个属性的setter和getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

1. Vue2 实现过程

我们来具体看一下Vue的实现过程吧

  1. 在Vue类中,接收一个options参数,里面包含data等选项,在构造函数里,我需要将data对象进行响应式处理。这一步应该是通过遍历data的所有属性,并用Object.defineProperty来重新定义它们,使得当属性被访问时收集依赖,被修改时触发更新。

  2. 对于每个属性,需要有一个Dep(依赖收集器)来管理所有的Watcher实例。每当属性被读取时,就将当前的Watcher添加到Dep中;当属性被修改时,Dep就会通知所有Watcher执行更新操作。

  3. 既然有Watcher实例,那么就有一个Watcher类,它负责订阅属性的变化,并在变化时执行回调函数,比如更新视图。在Watcher的构造函数中,需要将自己设置为全局的一个目标实例,然后触发属性的getter,从而将Watcher添加到Dep中。

  4. 还有,可能需要一个编译器(Compiler)来解析模板中的指令,比如v-model,然后初始化相应的数据绑定和事件监听。例如,对于v-model,需要将input元素的值与数据属性同步,并在输入时更新数据属性,从而触发数据的setter,进一步通知更新。

现在,我需要一步步构建这些类。首先是Vue类,它初始化数据,进行响应式处理。然后,每个数据属性都有一个对应的Dep实例。当数据被访问时,收集当前的Watcher;数据变化时,通知Dep里的所有Watcher更新。

然后,Watcher类的作用是连接数据属性和具体的更新函数,比如更新DOM。当数据变化时,Watcher会被触发,执行更新函数。

2. Vue2 组成结构

总体来说应有以下结构:

  1. Vue类:

作用:

  • 接收options,处理data。

  • 将data的属性转为getter/setter。

  • 调用observe函数进行响应式处理。

  1. Observer类:

作用:

  • 遍历对象的所有属性,转为响应式。

  • 每个属性有一个Dep实例。

  1. Dep类:

作用:

  • 收集Watcher实例(subs数组)。

  • 提供addSub方法添加Watcher。

  • 提供notify方法触发所有Watcher的update。

  1. Watcher类:

作用:

  • 在实例化时,将自身赋值给Dep.target。

  • 访问对应的数据属性,触发getter,从而将自身添加到Dep中。

  • 定义update方法,执行回调函数(比如更新DOM)。

接下来是具体的代码实现。可能还需要处理多个Watcher的情况,以及如何将它们正确地关联到对应的Dep。

然后,需要处理模板编译。比如,对于v-model,需要找到元素,添加事件监听,当输入时更新数据属性。同时,初始化input的值。对于{{}}文本,需要替换为对应的数据,并建立Watcher,当数据变化时更新文本内容。

不过,我们现在只是简单展示一下实现原理,直接操作DOM元素,而不是完整的编译器。例如,手动绑定一个input和显示元素,展示双向绑定的效果。

现在,我需要将上述思路转化为代码。可能先从Vue类开始:

javascript 复制代码
class Vue{
    constructor(options){
        this.$options = options;
        this._data = typeof options.data === 'function' ? options.data() : options.data;

        //调用Observer函数进行响应式处理
        new Observer(this._data);
        this.compile();

    }

    compile() {

        // 处理v-model绑定

        const inputs = document.querySelectorAll('[v-model]');

        inputs.forEach(input => {

            const key = input.getAttribute('v-model').trim();

            input.value = this._data[key];

            input.addEventListener('input', (e) => {

                this._data[key] = e.target.value;

            });

        });

        // 处理{{ }} 绑定

        const elements = document.querySelectorAll('p,span,div'); // 简化处理,实际应根据文本内容匹配

        elements.forEach(element => {

            const matches = element.innerHTML.match(/\{\{\s*(.*?)\s*\}\}/);

            if (matches) {

                const key = matches[1].trim();

                new Watcher(this._data, key, (newVal) => {

                    element.innerHTML = newVal;

                });

                // 初始化显示

                element.innerHTML = this._data[key];

            }

        });

    }
}
javascript 复制代码
 // Observer类:
 // 遍历对象的所有属性,转为响应式
 // 每个属性有一个Dep实例     

class Observer{

   constructor(data){
       this.walk(data);
   }

   walk(data){
       Object.keys(data).forEach(key =>{
           this.defineReactive(data,key,data[key]);
       })
       console.log(Object.keys(data))
   }

   defineReactive(obj,key,val){
       const dep = new Dep();
       Object.defineProperty(obj,key,{
           enumberable:true,
           configurable:true,
           get(){
               if(Dep.target){
                   dep.addSub(Dep.target)
               }
               return val;
           },
           set(newVal){
               if(newVal === val) return;
               val = newVal;
               dep.notify();
           }
       })
   }

}
javascript 复制代码
// 收集Watcher实例(subs数组)
// 提供addSub方法添加wathcer
 // 提供notify方法触发所有的Watcher 和 update。
 class Dep{

     constructor(){
         this.subs = [];
     }
     addSub(sub){
         this.subs.push(sub);
     }
     notify(){
         this.subs.forEach(sub => sub.update())
     }
 }

 Dep.target = null;
javascript 复制代码
// 在实例化时,将自身赋值给Dep.target
// 访问对应的数据属性,触发getter,从而将自身添加到Dep中。
 // 定义update方法,执行回调函数(比如更新Dom)
 class Watcher{
     constructor(data,key,cb){
         this.data = data;
         this.key = key;
         this.cb= cb;
         this.value = this.get();
     }

     get(){
         Dep.target = this; // 将当前Watcher实例赋值给Dep.target
         const value = this.data[this.key]; // 触发getter,从而将Watcher添加到Dep中
         Dep.target = null;//收集完清空
         return value;
     }

     update(){
         const newVal = this.data[this.key];
         if (newVal !== this.value) {

             this.value = newVal;

             this.cb(newVal);

         }
     }
 }

以下是完成的可运行代码:

javascript 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>vue2数据的双向绑定</title>
</head>
<body>
    <input type="text" v-model="message">
    <div id="app">
        <p>{{ message }}</p>
    </div>
    <script>

        // 收集Watcher实例(subs数组)
        // 提供addSub方法添加wathcer
        // 提供notify方法触发所有的Watcher 和 update。
        class Dep{

            constructor(){
                this.subs = [];
            }
            addSub(sub){
                this.subs.push(sub);
            }
            notify(){
                this.subs.forEach(sub => sub.update())
            }
        }

        Dep.target = null;


        // 在实例化时,将自身赋值给Dep.target
        // 访问对应的数据属性,触发getter,从而将自身添加到Dep中。
        // 定义update方法,执行回调函数(比如更新Dom)
        class Watcher{
            constructor(data,key,cb){
                this.data = data;
                this.key = key;
                this.cb= cb;
                this.value = this.get();
            }

            get(){
                Dep.target = this; // 将当前Watcher实例赋值给Dep.target
                const value = this.data[this.key]; // 触发getter,从而将Watcher添加到Dep中
                Dep.target = null;//收集完清空
                return value;
            }

            update(){
                const newVal = this.data[this.key];
                if (newVal !== this.value) {

                    this.value = newVal;

                    this.cb(newVal);

                }
            }

        }

        class Vue{
            constructor(options){
                this.$options = options;
                this._data = typeof options.data === 'function' ? options.data() : options.data;

                //调用Observer函数进行响应式处理
                new Observer(this._data);
                this.compile();

            }

            compile() {

                // 处理v-model绑定

                const inputs = document.querySelectorAll('[v-model]');

                inputs.forEach(input => {

                    const key = input.getAttribute('v-model').trim();

                    input.value = this._data[key];

                    input.addEventListener('input', (e) => {

                        this._data[key] = e.target.value;

                    });

                });

                // 处理{{ }} 绑定

                const elements = document.querySelectorAll('p,span,div'); // 简化处理,实际应根据文本内容匹配

                elements.forEach(element => {

                    const matches = element.innerHTML.match(/\{\{\s*(.*?)\s*\}\}/);

                    if (matches) {

                        const key = matches[1].trim();

                        new Watcher(this._data, key, (newVal) => {

                            element.innerHTML = newVal;

                        });

                        // 初始化显示

                        element.innerHTML = this._data[key];

                    }

                });

            }
        }

            // Observer类:
            // 遍历对象的所有属性,转为响应式
            // 每个属性有一个Dep实例     

        class Observer{

            constructor(data){
                this.walk(data);
            }

            walk(data){
                Object.keys(data).forEach(key =>{
                    this.defineReactive(data,key,data[key]);
                })
                console.log(Object.keys(data))
            }

            defineReactive(obj,key,val){
                const dep = new Dep();
                Object.defineProperty(obj,key,{
                    enumberable:true,
                    configurable:true,
                    get(){
                        if(Dep.target){
                            dep.addSub(Dep.target)
                        }
                        return val;
                    },
                    set(newVal){
                        if(newVal === val) return;
                        val = newVal;
                        dep.notify();
                    }
                })
            }

        }

      
        new Vue({
            data: () => ({
                message: "hello world"
            })
        });
    </script>
</body>
</html>

看完了此篇文章,您是不是对Vue2实现原理有了更深刻的理解呢,如果觉得对您有帮助,还请一键三连哦!非常感谢!

相关推荐
前端毕业班几秒前
uni-app onShareAppMessage hook 原理分析
前端·javascript
gogoing2 分钟前
React 分包加载优化
前端·react.js
gogoing5 分钟前
Babel 配置与工具
前端·javascript
亲亲小宝宝鸭5 分钟前
重新install,项目就跑不起来了?!
前端·npm
Mike117.19 分钟前
GBase 8a 物化视图依赖和 DDL 风险排查记录
java·服务器·前端
蜡台35 分钟前
Vue3 Hook 与 Store 状态管理:深度解析与选型指南
前端·javascript·vue.js
存在的五月雨35 分钟前
项目中 Vitest 配置详解:vitest.config.ts
开发语言·javascript·vue.js
無名路人1 小时前
小程序点餐页吸顶滚动
前端·微信小程序·ai编程
小小小前端啊1 小时前
前端手写代码大全
前端
李白的天不白1 小时前
大规模请求数据并发问题
java·前端·数据库