vue源码解析之数据绑定与数据劫持的原理

一、数据绑定的原理分析

  1. 数据绑定:一旦更新了 data 中的某个属性数据, 所有界面上直接使用或间接使用了此属性的节点都会更新。
  2. 数据劫持vue实现数据绑定的一种技术,其核心思想是通过 defineProperty()来监视 data 中所有属性(任意层次)数据的变化,一旦变 化就去更新界面。
  3. depwatcher的理解:
  • dep
    • dep在初始化给data的属性进行数据劫持的时候会被创建。
    • dep的个数与data中的属性一一对应。
    • dep的结构中,id是作为标识,subs: []n个相关的watcher容器。
  • watcher
    • watcher在初始化解析大括号表达式和一般指令的时候会被创建的
    • watcher的个数是与模版中的表达式一一对应,但是并不包含事件指令
    • watcher的结构
      this.cb = cb; // 用于更新界面的回调
      this.vm = vm; // vm
      this.exp = exp; // 对应的表达式
      this.depIds = {}; // 相关的n个dep的容器对象
      this.value = this.get(); // 当前表达式对应的value
      
  • depwatcher之间的关系
    • depwatcher之间存在多对多的关系,data属性--> dep --> n个watcher(模版中有多个表达式使用了此属性), 表达式 --> watcher --> n个dep(多层表达式)
    • data中属性的get()方法建立的
    • 初始化解析模块中的表达式创建watcher对象时建立关系的
  1. 实现数据绑定Observer,创建dep对象,核心过程如下:
 defineReactive: function(data, key, val) {
        // 创建与当前属性对应的dep对象
        var dep = new Dep();
        // 间接递归调用实现对data中所有层次属性的劫持
        var childObj = observe(val);
        // 给data重新定义属性(添加set/get)
        Object.defineProperty(data, key, {
            enumerable: true, // 可枚举
            configurable: false, // 不能再define
            get: function() { // 返回值,建立dep与watcher的关系
                if (Dep.target) {
                    // 建立关系
                    dep.depend();
                }
                // 返回属性值
                return val;
            },
            set: function(newVal) { // 监视key属性的变化,更新界面
                if (newVal === val) {
                    return;
                }
                val = newVal;
                // 新的值是object的话,进行监听
                childObj = observe(newVal);
                // 通过dep
                dep.notify();
            }
        });
    }
  1. dep对象上需要进行原型挂载,核心过程如下:
    // 将watcher添加到dep中
    addSub: function(sub) {
        this.subs.push(sub);
    },

    // 去建立dep和watcher之间的关系
    depend: function() {
        Dep.target.addDep(this);
    },

    removeSub: function(sub) {
        var index = this.subs.indexOf(sub);
        if (index != -1) {
            this.subs.splice(index, 1);
        }
    },

    notify: function() {
        // 通知所有相关的watcher(一个订阅者),通知watcher更新
        this.subs.forEach(function(sub) {
            sub.update();
        });
    }
  1. 对于watcher对象,核心过程如下:
function Watcher(vm, exp, cb) {
  this.cb = cb;  // callback,更新界面的回调
  this.vm = vm;
  this.exp = exp; // 表达式
  this.depIds = {};  // 包含所有相关的dep的容器对象  {0: d0, 1: d1, 2: d2}
  this.value = this.get(); // 得到表达式的初始值保存
}
  1. depwatcher之间需要建立关系,核心过程如下:
addDep: function (dep) {
    // 判断watcher与dep之间是否建立关系
    if (!this.depIds.hasOwnProperty(dep.id)) {
      // 建立dep到watcher,将watcher添加到dep中,用于更新
      dep.addSub(this);
      // 建立watcher到dep的关系,将dep添加到watcher中,用于防止重复建立关系
      this.depIds[dep.id] = dep;
    }
  },
  // 得到表达式对应的值,建立watcher与deep之间的关系
  get: function () {
    // 给dep指定当前的watcher
    Dep.target = this;
    // 获取当前表达式的值, 内部会导致属性的get()调用,内部调用get建立dep和watcher之间的关系
    var value = this.getVMVal();
    // 去除dep中指定的当前的watcher
    Dep.target = null;
    return value;
  },

二、数据绑定的实现

  1. Observer.js
function Observer(data) {
    // 保存data对象
    this.data = data;
    // 走起
    this.walk(data);
}

Observer.prototype = {
    walk: function(data) {
        var me = this;
        // 遍历data中所有属性
        Object.keys(data).forEach(function(key) {
            // 针对指定属性进行处理
            me.convert(key, data[key]);
        });
    },
    convert: function(key, val) {
        // 对指定属性实现响应式数据绑定
        this.defineReactive(this.data, key, val);
    },

    defineReactive: function(data, key, val) {
        // 创建与当前属性对应的dep对象
        var dep = new Dep();
        // 间接递归调用实现对data中所有层次属性的劫持
        var childObj = observe(val);
        // 给data重新定义属性(添加set/get)
        Object.defineProperty(data, key, {
            enumerable: true, // 可枚举
            configurable: false, // 不能再define
            get: function() { // 返回值,建立dep与watcher的关系
                if (Dep.target) {
                    // 建立关系
                    dep.depend();
                }
                // 返回属性值
                return val;
            },
            set: function(newVal) { // 监视key属性的变化,更新界面
                if (newVal === val) {
                    return;
                }
                val = newVal;
                // 新的值是object的话,进行监听
                childObj = observe(newVal);
                // 通过dep
                dep.notify();
            }
        });
    }
};

function observe(value, vm) {
    // value必须是对象, 因为监视的是对象内部的属性
    if (!value || typeof value !== 'object') {
        return;
    }
    // 创建一个对应的观察都对象
    return new Observer(value);
};


var uid = 0;

function Dep() {
    // 标识属性
    this.id = uid++;
    // 相关的所有watcher的数组
    this.subs = [];
}

Dep.prototype = {
    // 将watcher添加到dep中
    addSub: function(sub) {
        this.subs.push(sub);
    },

    // 去建立dep和watcher之间的关系
    depend: function() {
        Dep.target.addDep(this);
    },

    removeSub: function(sub) {
        var index = this.subs.indexOf(sub);
        if (index != -1) {
            this.subs.splice(index, 1);
        }
    },

    notify: function() {
        // 通知所有相关的watcher(一个订阅者),通知watcher更新
        this.subs.forEach(function(sub) {
            sub.update();
        });
    }
};

Dep.target = null;
  1. Watcher.js

function Watcher(vm, exp, cb) {
  this.cb = cb;  // callback,更新界面的回调
  this.vm = vm;
  this.exp = exp; // 表达式
  this.depIds = {};  // 包含所有相关的dep的容器对象  {0: d0, 1: d1, 2: d2}
  this.value = this.get(); // 得到表达式的初始值保存
}

Watcher.prototype = {
  update: function () {
    this.run();
  },
  run: function () {
    // 得到最新的值
    var value = this.get();
    // 得到旧值
    var oldVal = this.value;
    // 如果不相同
    if (value !== oldVal) {
      this.value = value;
      // 调用回调函数更新对应的界面
      this.cb.call(this.vm, value, oldVal);
    }
  },
  addDep: function (dep) {
    // 判断watcher与dep之间是否建立关系
    if (!this.depIds.hasOwnProperty(dep.id)) {
      // 建立dep到watcher,将watcher添加到dep中,用于更新
      dep.addSub(this);
      // 建立watcher到dep的关系,将dep添加到watcher中,用于防止重复建立关系
      this.depIds[dep.id] = dep;
    }
  },
  // 得到表达式对应的值,建立watcher与deep之间的关系
  get: function () {
    // 给dep指定当前的watcher
    Dep.target = this;
    // 获取当前表达式的值, 内部会导致属性的get()调用,内部调用get建立dep和watcher之间的关系
    var value = this.getVMVal();
    // 去除dep中指定的当前的watcher
    Dep.target = null;
    return value;
  },

  // 得到表达式对应的值
  getVMVal: function () {
    var exp = this.exp.split('.');
    var val = this.vm._data;
    exp.forEach(function (k) {
      val = val[k];
    });
    return val;
  }
};
  1. 数据绑定的实现
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>数据劫持与数据绑定</title>
</head>
<body>

    <div id="test">
        <p>{{ name }}</p>
        <p v-text="name"></p>
        <p v-text="friend.name"></p>
        <button v-on:click="update">更新</button>
    </div>

</body>
<script type="text/javascript" src="./js/mvvm/compile.js"></script>
<script type="text/javascript" src="js/mvvm/mvvm.js"></script>
<script type="text/javascript" src="./js/mvvm/observer.js"></script>
<script type="text/javascript" src="./js/mvvm/watcher.js"></script>
<script type="text/javascript">

    new MVVM({
        el: "#test",
        data: {
            name: "张三", // dep0
            friend: { // dep1
                name: "李四", // dep2
                age: 28 // dep3
            }
        },
        method: {
            update () {
                this.name = "王五";
            }
        }
    })

</script>
</html>
  1. 关于vue的源码分析,我在github上建立了一个项目,项目地址如下https://github.com/jiuchengTk279/vueSourceCode.git,欢迎大家访问下载,也希望可以多给予一些建议交流。
发布了146 篇原创文章 · 获赞 34 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/weixin_42614080/article/details/104145600